Merge pull request #1017 from weaveworks/680-connections-table

Add connection tables to details panel
This commit is contained in:
Paul Bellamy
2016-03-04 16:42:08 +00:00
28 changed files with 1255 additions and 678 deletions

View File

@@ -51,17 +51,18 @@ func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w htt
}
// Individual nodes.
func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
func handleNode(topologyID, nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
return func(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
var (
rpt = rep.Report(ctx)
node, ok = renderer.Render(rep.Report(ctx))[nodeID]
rendered = renderer.Render(rep.Report(ctx))
node, ok = rendered[nodeID]
)
if !ok {
http.NotFound(w, r)
return
}
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, node)})
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, rpt, rendered, node)})
}
}

View File

@@ -54,27 +54,6 @@ func TestAll(t *testing.T) {
}
}
func TestAPITopologyContainers(t *testing.T) {
ts := topologyServer()
{
body := getRawJSON(t, ts, "/api/topology/containers")
var topo app.APITopology
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&topo); err != nil {
t.Fatal(err)
}
want := expected.RenderedContainers.Copy()
for id, node := range want {
node.ControlNode = ""
want[id] = node
}
if have := topo.Nodes.Prune(); !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}
func TestAPITopologyProcesses(t *testing.T) {
ts := topologyServer()
defer ts.Close()
@@ -124,13 +103,13 @@ func TestAPITopologyHosts(t *testing.T) {
}
}
{
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID)
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostID)
var node app.APINode
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&node); err != nil {
t.Fatal(err)
}
equals(t, expected.ServerHostRenderedID, node.Node.ID)
equals(t, expected.ServerHostID, node.Node.ID)
equals(t, "server", node.Node.Label)
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables

View File

@@ -122,7 +122,7 @@ func TopologyHandler(c Reporter, preRoutes *mux.Router, postRoutes http.Handler)
}
handler := gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters(
c, topologyID, handleNode(nodeID),
c, topologyID, handleNode(topologyID, nodeID),
)))
handler.ServeHTTP(w, r)
})

View File

@@ -174,6 +174,14 @@ export default class NodeDetails extends React.Component {
<NodeDetailsInfo rows={details.metadata} />
</div>}
{details.connections && details.connections.map(connections => {
return (
<div className="node-details-content-section" key={connections.id}>
<NodeDetailsTable {...connections} />
</div>
);
})}
{details.children && details.children.map(children => {
return (
<div className="node-details-content-section" key={children.topologyId}>

View File

@@ -5,6 +5,10 @@ import ShowMore from '../show-more';
import NodeDetailsTableNodeLink from './node-details-table-node-link';
import NodeDetailsTableNodeMetric from './node-details-table-node-metric';
function isNumberField(field) {
return field.dataType && field.dataType === 'number';
}
export default class NodeDetailsTable extends React.Component {
constructor(props, context) {
@@ -32,14 +36,28 @@ export default class NodeDetailsTable extends React.Component {
}
getDefaultSortBy() {
// first metric
// default sorter specified by columns
const defaultSortColumn = _.find(this.props.columns, {defaultSort: true});
if (defaultSortColumn) {
return defaultSortColumn.id;
}
// otherwise choose first metric
return _.get(this.props.nodes, [0, 'metrics', 0, 'id']);
}
getMetaDataSorters() {
// returns an array of sorters that will take a node
return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => {
return node => node.metadata[index] ? node.metadata[index].value : null;
return node => {
const nodeMetadataField = node.metadata[index];
if (nodeMetadataField) {
if (isNumberField(nodeMetadataField)) {
return parseFloat(nodeMetadataField.value);
}
return nodeMetadataField.value;
}
return null;
};
});
}
@@ -49,6 +67,9 @@ export default class NodeDetailsTable extends React.Component {
if (sortBy !== null) {
const field = _.union(node.metrics, node.metadata).find(f => f.id === sortBy);
if (field) {
if (isNumberField(field)) {
return parseFloat(field.value);
}
return field.value;
}
}

View File

@@ -22,11 +22,9 @@ do_connections() {
}
do_connections&
wait_for_containers $HOST1 60 nginx "Inbound"
wait_for_containers $HOST1 60 nginx "The Internet"
has_container $HOST1 nginx
has_container $HOST1 "Inbound"
has_connection containers $HOST1 "Inbound" nginx
has_connection_by_id containers $HOST1 "in-theinternet" $(node_id containers $HOST1 nginx)
kill %do_connections

View File

@@ -59,14 +59,13 @@ container_id() {
}
# this checks we have an edge from container 1 to container 2
has_connection() {
has_connection_by_id() {
local view="$1"
local host="$2"
local from="$3"
local to="$4"
local from_id="$3"
local to_id="$4"
local timeout="${5:-60}"
local from_id=$(node_id "${view}" "${host}" "${from}")
local to_id=$(node_id "${view}" "${host}" "${to}")
for i in $(seq $timeout); do
local nodes="$(curl -s http://$host:4040/api/topology/${view}?system=show)"
local edge=$(echo "$nodes" | jq -r ".nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])" 2>/dev/null)
@@ -82,6 +81,18 @@ has_connection() {
assert "curl -s http://$host:4040/api/topology/${view}?system=show | jq -r '.nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])'" true
}
has_connection() {
local view="$1"
local host="$2"
local from="$3"
local to="$4"
local timeout="${5:-60}"
local from_id="$(node_id "${view}" "${host}" "${from}")"
local to_id="$(node_id "${view}" "${host}" "${to}")"
has_connection_by_id "${view}" "${host}" "${from_id}" "${to_id}" "${timeout}"
}
wait_for() {
local view="$1"
local host="$2"

View File

@@ -0,0 +1,197 @@
package detailed
import (
"sort"
"strconv"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
const (
portKey = "port"
portLabel = "Port"
countKey = "count"
countLabel = "Count"
number = "number"
)
// Exported for testing
var (
NormalColumns = []Column{
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
InternetColumns = []Column{
{ID: "foo", Label: "Remote"},
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
)
type connectionsRow struct {
remoteNode, localNode *render.RenderableNode
remoteAddr, localAddr string
port string // always the server-side port
}
func incomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpointIDs := endpointChildIDsOf(n)
// For each node which has an edge TO me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !node.Adjacency.Contains(n.ID) {
continue
}
remoteNode := node.Copy()
// Work out what port they are talking to, and count the number of
// connections to that port.
// This is complicated as for internet nodes we break out individual
// address, both when the internet node is remote (an incoming
// connection from the internet) and 'local' (ie you are loading
// details on the internet node)
for _, child := range endpointChildrenOf(node) {
for _, localEndpointID := range child.Adjacency.Intersection(localEndpointIDs) {
_, localAddr, port, ok := render.ParseEndpointID(localEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}
columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "incoming-connections",
TopologyID: topologyID,
Label: "Inbound",
Columns: columnHeaders,
Nodes: connectionRows(counts, isInternetNode(n)),
}
}
func outgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpoints := endpointChildrenOf(n)
// For each node which has an edge FROM me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !n.Adjacency.Contains(node.ID) {
continue
}
remoteNode := node.Copy()
remoteEndpointIDs := endpointChildIDsOf(remoteNode)
for _, localEndpoint := range localEndpoints {
_, localAddr, _, ok := render.ParseEndpointID(localEndpoint.ID)
if !ok {
continue
}
for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
_, _, port, ok := render.ParseEndpointID(remoteEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}
columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "outgoing-connections",
TopologyID: topologyID,
Label: "Outbound",
Columns: columnHeaders,
Nodes: connectionRows(counts, isInternetNode(n)),
}
}
func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode {
result := []render.RenderableNode{}
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child)
}
})
return result
}
func endpointChildIDsOf(n render.RenderableNode) report.IDList {
result := report.MakeIDList()
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child.ID)
}
})
return result
}
func isInternetNode(n render.RenderableNode) bool {
return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID
}
func connectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary {
nodes := []NodeSummary{}
for row, count := range in {
id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true
if row.remoteAddr != "" {
id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false
}
metadata := []MetadataRow{}
if includeLocal {
metadata = append(metadata,
MetadataRow{
ID: "foo",
Value: row.localAddr,
Datatype: number,
})
}
metadata = append(metadata,
MetadataRow{
ID: portKey,
Value: row.port,
Datatype: number,
},
MetadataRow{
ID: countKey,
Value: strconv.Itoa(count),
Datatype: number,
},
)
nodes = append(nodes, NodeSummary{
ID: id,
Label: label,
Linkable: linkable,
Metadata: metadata,
})
}
sort.Sort(nodeSummariesByID(nodes))
return nodes
}

View File

@@ -98,16 +98,22 @@ type Counter struct {
// MetadataRows implements MetadataRowTemplate
func (c Counter) MetadataRows(n report.Node) []MetadataRow {
if val, ok := n.Counters.Lookup(c.ID); ok {
return []MetadataRow{{ID: c.ID, Value: strconv.Itoa(val), Prime: c.Prime}}
return []MetadataRow{{
ID: c.ID,
Value: strconv.Itoa(val),
Prime: c.Prime,
Datatype: number,
}}
}
return nil
}
// MetadataRow is a row for the metadata table.
type MetadataRow struct {
ID string
Value string
Prime bool
ID string
Value string
Prime bool
Datatype string
}
// Copy returns a value copy of a metadata row.
@@ -129,20 +135,22 @@ func (*MetadataRow) UnmarshalJSON(b []byte) error {
}
type labelledMetadataRow struct {
ID string `json:"id"`
Label string `json:"label"`
Value string `json:"value"`
Prime bool `json:"prime,omitempty"`
ID string `json:"id"`
Label string `json:"label"`
Value string `json:"value"`
Prime bool `json:"prime,omitempty"`
Datatype string `json:"dataType,omitempty"`
}
// CodecEncodeSelf marshals this MetadataRow. It adds a label before
// rendering.
func (m *MetadataRow) CodecEncodeSelf(encoder *codec.Encoder) {
in := labelledMetadataRow{
ID: m.ID,
Label: Label(m.ID),
Value: m.Value,
Prime: m.Prime,
ID: m.ID,
Label: Label(m.ID),
Value: m.Value,
Prime: m.Prime,
Datatype: m.Datatype,
}
encoder.Encode(in)
}
@@ -152,9 +160,10 @@ func (m *MetadataRow) CodecDecodeSelf(decoder *codec.Decoder) {
var in labelledMetadataRow
decoder.Decode(&in)
*m = MetadataRow{
ID: in.ID,
Value: in.Value,
Prime: in.Prime,
ID: in.ID,
Value: in.Value,
Prime: in.Prime,
Datatype: in.Datatype,
}
}

View File

@@ -16,11 +16,12 @@ import (
// we want deep information about an individual node.
type Node struct {
NodeSummary
Rank string `json:"rank,omitempty"`
Pseudo bool `json:"pseudo,omitempty"`
Controls []ControlInstance `json:"controls"`
Children []NodeSummaryGroup `json:"children,omitempty"`
Parents []Parent `json:"parents,omitempty"`
Rank string `json:"rank,omitempty"`
Pseudo bool `json:"pseudo,omitempty"`
Controls []ControlInstance `json:"controls"`
Children []NodeSummaryGroup `json:"children,omitempty"`
Parents []Parent `json:"parents,omitempty"`
Connections []NodeSummaryGroup `json:"connections,omitempty"`
}
// ControlInstance contains a control description, and all the info
@@ -78,10 +79,11 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) {
// MakeNode transforms a renderable node to a detailed node. It uses
// aggregate metadata, plus the set of origin node IDs, to produce tables.
func MakeNode(r report.Report, n render.RenderableNode) Node {
summary, _ := MakeNodeSummary(n.Node)
func MakeNode(topologyID string, r report.Report, ns render.RenderableNodes, n render.RenderableNode) Node {
summary, _ := MakeNodeSummary(n)
summary.ID = n.ID
summary.Label = n.LabelMajor
return Node{
NodeSummary: summary,
Rank: n.Rank,
@@ -89,6 +91,10 @@ func MakeNode(r report.Report, n render.RenderableNode) Node {
Controls: controls(r, n),
Children: children(n),
Parents: Parents(r, n),
Connections: []NodeSummaryGroup{
incomingConnectionsTable(topologyID, n, ns),
outgoingConnectionsTable(topologyID, n, ns),
},
}
}
@@ -133,17 +139,25 @@ var (
topologyID string
NodeSummaryGroup
}{
{report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{host.CPUUsage, host.MemoryUsage}}},
{report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{
MakeColumn(host.CPUUsage), MakeColumn(host.MemoryUsage),
}}},
{report.Pod, NodeSummaryGroup{TopologyID: "pods", Label: "Pods"}},
{report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{docker.CPUTotalUsage, docker.MemoryUsage}}},
{report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{process.PID, process.CPUUsage, process.MemoryUsage}}},
{report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{render.ContainersKey}}},
{report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{
MakeColumn(docker.CPUTotalUsage), MakeColumn(docker.MemoryUsage),
}}},
{report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{
MakeColumn(process.PID), MakeColumn(process.CPUUsage), MakeColumn(process.MemoryUsage),
}}},
{report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{
MakeColumn(render.ContainersKey),
}}},
}
)
func children(n render.RenderableNode) []NodeSummaryGroup {
summaries := map[string][]NodeSummary{}
n.Children.ForEach(func(child report.Node) {
n.Children.ForEach(func(child render.RenderableNode) {
if child.ID != n.ID {
if summary, ok := MakeNodeSummary(child); ok {
summaries[child.Topology] = append(summaries[child.Topology], summary)
@@ -160,5 +174,6 @@ func children(n render.RenderableNode) []NodeSummaryGroup {
nodeSummaryGroups = append(nodeSummaryGroups, group)
}
}
return nodeSummaryGroups
}

View File

@@ -10,21 +10,29 @@ import (
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/detailed"
"github.com/weaveworks/scope/render/expected"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
)
func TestMakeDetailedHostNode(t *testing.T) {
renderableNode := render.HostRenderer.Render(fixture.Report)[render.MakeHostID(fixture.ClientHostID)]
have := detailed.MakeNode(fixture.Report, renderableNode)
renderableNodes := render.HostRenderer.Render(fixture.Report)
renderableNode := renderableNodes[render.MakeHostID(fixture.ClientHostID)]
have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)
containerImageNodeSummary, _ := detailed.MakeNodeSummary(
render.ContainerImageRenderer.Render(fixture.Report)[render.MakeContainerImageID(fixture.ClientContainerImageName)].Node,
render.ContainerImageRenderer.Render(fixture.Report)[expected.ClientContainerImageID],
)
containerNodeSummary, _ := detailed.MakeNodeSummary(
render.ContainerRenderer.Render(fixture.Report)[expected.ClientContainerID],
)
process1NodeSummary, _ := detailed.MakeNodeSummary(
render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess1ID],
)
containerNodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Container.Nodes[fixture.ClientContainerNodeID])
process1NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID])
process1NodeSummary.Linkable = true
process2NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID])
process2NodeSummary, _ := detailed.MakeNodeSummary(
render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess2ID],
)
process2NodeSummary.Linkable = true
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
@@ -85,22 +93,56 @@ func TestMakeDetailedHostNode(t *testing.T) {
{
Label: "Containers",
TopologyID: "containers",
Columns: []detailed.Column{docker.CPUTotalUsage, docker.MemoryUsage},
Columns: []detailed.Column{detailed.MakeColumn(docker.CPUTotalUsage), detailed.MakeColumn(docker.MemoryUsage)},
Nodes: []detailed.NodeSummary{containerNodeSummary},
},
{
Label: "Processes",
TopologyID: "processes",
Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage},
Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)},
Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary},
},
{
Label: "Container Images",
TopologyID: "containers-by-image",
Columns: []detailed.Column{render.ContainersKey},
Columns: []detailed.Column{detailed.MakeColumn(render.ContainersKey)},
Nodes: []detailed.NodeSummary{containerImageNodeSummary},
},
},
Connections: []detailed.NodeSummaryGroup{
{
ID: "incoming-connections",
TopologyID: "hosts",
Label: "Inbound",
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{},
},
{
ID: "outgoing-connections",
TopologyID: "hosts",
Label: "Outbound",
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{
{
ID: "host:server.hostname.com",
Label: "server",
Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
Value: "80",
Datatype: "number",
},
{
ID: "count",
Value: "2",
Datatype: "number",
},
},
},
},
},
},
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
@@ -109,11 +151,12 @@ func TestMakeDetailedHostNode(t *testing.T) {
func TestMakeDetailedContainerNode(t *testing.T) {
id := render.MakeContainerID(fixture.ServerContainerID)
renderableNode, ok := render.ContainerRenderer.Render(fixture.Report)[id]
renderableNodes := render.ContainerRenderer.Render(fixture.Report)
renderableNode, ok := renderableNodes[id]
if !ok {
t.Fatalf("Node not found: %s", id)
}
have := detailed.MakeNode(fixture.Report, renderableNode)
have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode)
want := detailed.Node{
NodeSummary: detailed.NodeSummary{
ID: id,
@@ -145,14 +188,13 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
},
Rank: "imageid456",
Pseudo: false,
Controls: []detailed.ControlInstance{},
Children: []detailed.NodeSummaryGroup{
{
Label: "Processes",
TopologyID: "processes",
Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage},
Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)},
Nodes: []detailed.NodeSummary{
{
ID: fmt.Sprintf("process:%s:%s", "server.hostname.com", fixture.ServerPID),
@@ -178,6 +220,57 @@ func TestMakeDetailedContainerNode(t *testing.T) {
TopologyID: "hosts",
},
},
Connections: []detailed.NodeSummaryGroup{
{
ID: "incoming-connections",
TopologyID: "containers",
Label: "Inbound",
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{
{
ID: "container:a1b2c3d4e5",
Label: "client",
Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
Value: "80",
Datatype: "number",
},
{
ID: "count",
Value: "2",
Datatype: "number",
},
},
},
{
ID: "in-theinternet",
Label: "The Internet",
Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
Value: "80",
Datatype: "number",
},
{
ID: "count",
Value: "1",
Datatype: "number",
},
},
},
},
},
{
ID: "outgoing-connections",
TopologyID: "containers",
Label: "Outbound",
Columns: detailed.NormalColumns,
Nodes: []detailed.NodeSummary{},
},
},
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))

View File

@@ -3,8 +3,6 @@ package detailed
import (
"fmt"
"github.com/ugorji/go/codec"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
@@ -15,6 +13,7 @@ import (
// NodeSummaryGroup is a topology-typed group of children for a Node.
type NodeSummaryGroup struct {
ID string `json:"id"`
Label string `json:"label"`
Nodes []NodeSummary `json:"nodes"`
TopologyID string `json:"topologyId"`
@@ -36,29 +35,18 @@ func (g NodeSummaryGroup) Copy() NodeSummaryGroup {
// Column provides special json serialization for column ids, so they include
// their label for the frontend.
type Column string
// CodecEncodeSelf implements codec.Selfer
func (c *Column) CodecEncodeSelf(encoder *codec.Encoder) {
in := map[string]string{"id": string(*c), "label": Label(string(*c))}
encoder.Encode(in)
type Column struct {
ID string `json:"id"`
Label string `json:"label"`
DefaultSort bool `json:"defaultSort"`
}
// CodecDecodeSelf implements codec.Selfer
func (c *Column) CodecDecodeSelf(decoder *codec.Decoder) {
m := map[string]string{}
decoder.Decode(&m)
*c = Column(m["id"])
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
func (Column) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
func (*Column) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
// MakeColumn makes a Column by looking up the label by id.
func MakeColumn(id string) Column {
return Column{
ID: id,
Label: Label(id),
}
}
// NodeSummary is summary information about a child for a Node.
@@ -72,7 +60,7 @@ type NodeSummary struct {
}
// MakeNodeSummary summarizes a node, if possible.
func MakeNodeSummary(n report.Node) (NodeSummary, bool) {
func MakeNodeSummary(n render.RenderableNode) (NodeSummary, bool) {
renderers := map[string]func(report.Node) NodeSummary{
report.Process: processNodeSummary,
report.Container: containerNodeSummary,
@@ -81,7 +69,7 @@ func MakeNodeSummary(n report.Node) (NodeSummary, bool) {
report.Host: hostNodeSummary,
}
if renderer, ok := renderers[n.Topology]; ok {
return renderer(n), true
return renderer(n.Node), true
}
return NodeSummary{}, false
}

View File

@@ -16,16 +16,111 @@ var (
hexagon = "hexagon"
cloud = "cloud"
uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostName)
unknownPseudoNode1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP, "80")
unknownPseudoNode2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP, "80")
unknownPseudoNode1 = func(adjacent string) render.RenderableNode {
Client54001EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54001)
Client54002EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54002)
ServerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.ServerPort)
UnknownClient1EndpointID = render.MakeEndpointID("", fixture.UnknownClient1IP, fixture.UnknownClient1Port)
UnknownClient2EndpointID = render.MakeEndpointID("", fixture.UnknownClient2IP, fixture.UnknownClient2Port)
UnknownClient3EndpointID = render.MakeEndpointID("", fixture.UnknownClient3IP, fixture.UnknownClient3Port)
RandomClientEndpointID = render.MakeEndpointID("", fixture.RandomClientIP, fixture.RandomClientPort)
NonContainerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort)
GoogleEndpointID = render.MakeEndpointID("", fixture.GoogleIP, fixture.GooglePort)
RenderedEndpoints = (render.RenderableNodes{
Client54001EndpointID: {
ID: Client54001EndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(10),
EgressByteCount: newu64(100),
},
},
Client54002EndpointID: {
ID: Client54002EndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(20),
EgressByteCount: newu64(200),
},
},
ServerEndpointID: {
ID: ServerEndpointID,
Shape: circle,
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
IngressPacketCount: newu64(210),
IngressByteCount: newu64(2100),
},
},
UnknownClient1EndpointID: {
ID: UnknownClient1EndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
},
UnknownClient2EndpointID: {
ID: UnknownClient2EndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(40),
EgressByteCount: newu64(400),
},
},
UnknownClient3EndpointID: {
ID: UnknownClient3EndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(50),
EgressByteCount: newu64(500),
},
},
RandomClientEndpointID: {
ID: RandomClientEndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerEndpointID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
EgressByteCount: newu64(600),
},
},
NonContainerEndpointID: {
ID: NonContainerEndpointID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(GoogleEndpointID),
EdgeMetadata: report.EdgeMetadata{},
},
GoogleEndpointID: {
ID: GoogleEndpointID,
Shape: circle,
Node: report.MakeNode(),
},
}).Prune()
ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID)
unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP, fixture.ServerPort)
unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP, fixture.ServerPort)
unknownPseudoNode1 = func(adjacent string) render.RenderableNode {
return render.RenderableNode{
ID: unknownPseudoNode1ID,
LabelMajor: "10.10.10.10",
LabelMajor: fixture.UnknownClient1IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[UnknownClient1EndpointID],
RenderedEndpoints[UnknownClient2EndpointID],
),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(70),
EgressByteCount: newu64(700),
@@ -35,10 +130,13 @@ var (
unknownPseudoNode2 = func(adjacent string) render.RenderableNode {
return render.RenderableNode{
ID: unknownPseudoNode2ID,
LabelMajor: "10.10.10.11",
LabelMajor: fixture.UnknownClient3IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[UnknownClient3EndpointID],
),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(50),
EgressByteCount: newu64(500),
@@ -49,10 +147,13 @@ var (
return render.RenderableNode{
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
LabelMinor: render.RequestsMinor,
LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(adjacent),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[RandomClientEndpointID],
),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
EgressByteCount: newu64(600),
@@ -62,16 +163,15 @@ var (
theOutgoingInternetNode = render.RenderableNode{
ID: render.OutgoingInternetID,
LabelMajor: render.OutboundMajor,
LabelMinor: render.RequestsMinor,
LabelMinor: render.OutboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{},
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[GoogleEndpointID],
),
}
ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID)
RenderedProcesses = (render.RenderableNodes{
ClientProcess1ID: {
@@ -79,9 +179,11 @@ var (
LabelMajor: fixture.Client1Name,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID),
Rank: fixture.Client1Name,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(10),
EgressByteCount: newu64(100),
@@ -92,9 +194,11 @@ var (
LabelMajor: fixture.Client2Name,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID),
Rank: fixture.Client2Name,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54002EndpointID],
),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(20),
EgressByteCount: newu64(200),
@@ -105,22 +209,26 @@ var (
LabelMajor: fixture.ServerName,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID),
Rank: fixture.ServerName,
Pseudo: false,
Shape: square,
Node: report.MakeNode(),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
),
EdgeMetadata: report.EdgeMetadata{
IngressPacketCount: newu64(210),
IngressByteCount: newu64(2100),
},
},
nonContainerProcessID: {
ID: nonContainerProcessID,
LabelMajor: fixture.NonContainerName,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID),
Rank: fixture.NonContainerName,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
ID: nonContainerProcessID,
LabelMajor: fixture.NonContainerName,
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID),
Rank: fixture.NonContainerName,
Shape: square,
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
),
EdgeMetadata: report.EdgeMetadata{},
},
unknownPseudoNode1ID: unknownPseudoNode1(ServerProcessID),
@@ -129,22 +237,19 @@ var (
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
ServerProcessRenderedID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID)
ClientProcess1RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID)
ClientProcess2RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID)
RenderedProcessNames = (render.RenderableNodes{
fixture.Client1Name: {
ID: fixture.Client1Name,
LabelMajor: fixture.Client1Name,
LabelMinor: "2 processes",
Rank: fixture.Client1Name,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
RenderedEndpoints[Client54002EndpointID],
RenderedProcesses[ClientProcess1ID],
RenderedProcesses[ClientProcess2ID],
),
Node: report.MakeNode().WithAdjacent(fixture.ServerName),
EdgeMetadata: report.EdgeMetadata{
@@ -157,11 +262,11 @@ var (
LabelMajor: fixture.ServerName,
LabelMinor: "1 process",
Rank: fixture.ServerName,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ServerProcessID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -174,11 +279,11 @@ var (
LabelMajor: fixture.NonContainerName,
LabelMinor: "1 process",
Rank: fixture.NonContainerName,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
@@ -189,37 +294,37 @@ var (
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
ServerContainerRenderedID = render.MakeContainerID(fixture.ServerContainerID)
ClientContainerRenderedID = render.MakeContainerID(fixture.ClientContainerID)
ClientContainerID = render.MakeContainerID(fixture.ClientContainerID)
ServerContainerID = render.MakeContainerID(fixture.ServerContainerID)
uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID)
RenderedContainers = (render.RenderableNodes{
ClientContainerRenderedID: {
ID: ClientContainerRenderedID,
ClientContainerID: {
ID: ClientContainerID,
LabelMajor: "client",
LabelMinor: fixture.ClientHostName,
Rank: fixture.ClientContainerImageName,
Pseudo: false,
Shape: hexagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
RenderedEndpoints[Client54002EndpointID],
RenderedProcesses[ClientProcess1ID],
RenderedProcesses[ClientProcess2ID],
),
Node: report.MakeNode().WithAdjacent(ServerContainerRenderedID),
Node: report.MakeNode().WithAdjacent(ServerContainerID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
ControlNode: fixture.ClientContainerNodeID,
},
ServerContainerRenderedID: {
ID: ServerContainerRenderedID,
ServerContainerID: {
ID: ServerContainerID,
LabelMajor: "server",
LabelMinor: fixture.ServerHostName,
Rank: fixture.ServerContainerImageName,
Pseudo: false,
Shape: hexagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ServerProcessID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -232,54 +337,57 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
Pseudo: true,
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
render.IncomingInternetID: theIncomingInternetNode(ServerContainerRenderedID),
// unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID),
// unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID),
render.IncomingInternetID: theIncomingInternetNode(ServerContainerID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
ClientContainerImageRenderedName = render.MakeContainerImageID(fixture.ClientContainerImageName)
ServerContainerImageRenderedName = render.MakeContainerImageID(fixture.ServerContainerImageName)
ClientContainerImageID = render.MakeContainerImageID(fixture.ClientContainerImageName)
ServerContainerImageID = render.MakeContainerImageID(fixture.ServerContainerImageName)
RenderedContainerImages = (render.RenderableNodes{
ClientContainerImageRenderedName: {
ID: ClientContainerImageRenderedName,
ClientContainerImageID: {
ID: ClientContainerImageID,
LabelMajor: fixture.ClientContainerImageName,
LabelMinor: "1 container",
Rank: fixture.ClientContainerImageName,
Pseudo: false,
Shape: hexagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
RenderedEndpoints[Client54002EndpointID],
RenderedProcesses[ClientProcess1ID],
RenderedProcesses[ClientProcess2ID],
RenderedContainers[ClientContainerID],
),
Node: report.MakeNode().WithAdjacent(ServerContainerImageRenderedName),
Node: report.MakeNode().WithAdjacent(ServerContainerImageID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
},
ServerContainerImageRenderedName: {
ID: ServerContainerImageRenderedName,
ServerContainerImageID: {
ID: ServerContainerImageID,
LabelMajor: fixture.ServerContainerImageName,
LabelMinor: "1 container",
Rank: fixture.ServerContainerImageName,
Pseudo: false,
Shape: hexagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ServerProcessID],
RenderedContainers[ServerContainerID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -291,36 +399,113 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
Pseudo: true,
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageRenderedName),
// unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID),
// unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID),
render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
ServerHostRenderedID = render.MakeHostID(fixture.ServerHostID)
ClientHostRenderedID = render.MakeHostID(fixture.ClientHostID)
pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP)
pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP)
ClientAddressID = render.MakeAddressID(fixture.ClientHostID, fixture.ClientIP)
ServerAddressID = render.MakeAddressID(fixture.ServerHostID, fixture.ServerIP)
unknownPseudoAddress1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP)
unknownPseudoAddress2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP)
RenderedAddresses = (render.RenderableNodes{
ClientAddressID: {
ID: ClientAddressID,
LabelMajor: fixture.ClientIP,
LabelMinor: fixture.ClientHostID,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerAddressID),
},
ServerAddressID: {
ID: ServerAddressID,
LabelMajor: fixture.ServerIP,
LabelMinor: fixture.ServerHostID,
Shape: circle,
Node: report.MakeNode(),
},
unknownPseudoAddress1ID: {
ID: unknownPseudoAddress1ID,
LabelMajor: "10.10.10.10",
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerAddressID),
},
unknownPseudoAddress2ID: {
ID: unknownPseudoAddress2ID,
LabelMajor: "10.10.10.11",
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerAddressID),
},
render.IncomingInternetID: {
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(ServerAddressID),
EdgeMetadata: report.EdgeMetadata{},
},
}).Prune()
ServerHostID = render.MakeHostID(fixture.ServerHostID)
ClientHostID = render.MakeHostID(fixture.ClientHostID)
pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP)
pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP)
RenderedHosts = (render.RenderableNodes{
ServerHostRenderedID: {
ID: ServerHostRenderedID,
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],
RenderedAddresses[ClientAddressID],
),
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",
Pseudo: false,
Shape: circle,
Children: report.MakeNodeSet(
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
fixture.Report.Container.Nodes[fixture.ServerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ServerProcessID],
RenderedContainers[ServerContainerID],
RenderedContainerImages[ServerContainerImageID],
RenderedAddresses[ServerAddressID],
// See #1102
// RemappedEndpoints[NonContainerEndpointID],
// RenderedEndpoints[NonContainerPseudoEndpointID],
// RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -328,34 +513,17 @@ var (
IngressByteCount: newu64(2100),
},
},
ClientHostRenderedID: {
ID: ClientHostRenderedID,
LabelMajor: "client", // before first .
LabelMinor: "hostname.com", // after first .
Rank: "hostname.com",
Pseudo: false,
Shape: circle,
Children: report.MakeNodeSet(
fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
),
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(30),
EgressByteCount: newu64(300),
},
},
pseudoHostID1: {
ID: pseudoHostID1,
LabelMajor: fixture.UnknownClient1IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
Children: report.MakeNodeSet(
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
Children: render.MakeRenderableNodeSet(
// RenderedEndpoints[unknownPseudoNode2ID],
// RenderedAddresses[unknownPseudoAddress1ID],
),
},
pseudoHostID2: {
@@ -363,17 +531,24 @@ var (
LabelMajor: fixture.UnknownClient3IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
Children: render.MakeRenderableNodeSet(
// RenderedEndpoints[unknownPseudoNode2ID],
// RenderedAddresses[unknownPseudoAddress2ID],
),
},
render.IncomingInternetID: {
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
LabelMinor: render.RequestsMinor,
LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
Node: report.MakeNode().WithAdjacent(ServerHostID),
EdgeMetadata: report.EdgeMetadata{},
Children: render.MakeRenderableNodeSet(
// RenderedEndpoints[render.TheInternetID],
),
},
}).Prune()
@@ -386,14 +561,13 @@ var (
LabelMajor: "pong-a",
LabelMinor: "1 container",
Rank: "ping/pong-a",
Pseudo: false,
Shape: heptagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID],
fixture.Report.Pod.Nodes[fixture.ClientPodNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
RenderedEndpoints[Client54002EndpointID],
RenderedProcesses[ClientProcess1ID],
RenderedProcesses[ClientProcess2ID],
RenderedContainers[ClientContainerID],
),
Node: report.MakeNode().WithAdjacent(ServerPodRenderedID),
EdgeMetadata: report.EdgeMetadata{
@@ -406,13 +580,11 @@ var (
LabelMajor: "pong-b",
LabelMinor: "1 container",
Rank: "ping/pong-b",
Pseudo: false,
Shape: heptagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID],
fixture.Report.Pod.Nodes[fixture.ServerPodNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ServerProcessID],
RenderedContainers[ServerContainerID],
),
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
@@ -424,16 +596,18 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
// unknownPseudoNode1ID: unknownPseudoNode1(ServerPodRenderedID),
// unknownPseudoNode2ID: unknownPseudoNode2(ServerPodRenderedID),
render.IncomingInternetID: theIncomingInternetNode(ServerPodRenderedID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()
@@ -446,19 +620,19 @@ var (
LabelMajor: "pongservice",
LabelMinor: "2 pods",
Rank: fixture.ServiceID,
Pseudo: false,
Shape: heptagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID],
fixture.Report.Pod.Nodes[fixture.ClientPodNodeID],
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID],
fixture.Report.Pod.Nodes[fixture.ServerPodNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[Client54001EndpointID],
RenderedEndpoints[Client54002EndpointID],
RenderedEndpoints[ServerEndpointID],
RenderedProcesses[ClientProcess1ID],
RenderedProcesses[ClientProcess2ID],
RenderedProcesses[ServerProcessID],
RenderedContainers[ClientContainerID],
RenderedContainers[ServerContainerID],
RenderedPods[ClientPodRenderedID],
RenderedPods[ServerPodRenderedID],
),
Node: report.MakeNode().WithAdjacent(ServiceRenderedID),
EdgeMetadata: report.EdgeMetadata{
@@ -472,16 +646,18 @@ var (
ID: uncontainedServerID,
LabelMajor: render.UncontainedMajor,
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
Children: render.MakeRenderableNodeSet(
RenderedEndpoints[NonContainerEndpointID],
RenderedProcesses[nonContainerProcessID],
),
Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
// unknownPseudoNode1ID: unknownPseudoNode1(ServiceRenderedID),
// unknownPseudoNode2ID: unknownPseudoNode2(ServiceRenderedID),
render.IncomingInternetID: theIncomingInternetNode(ServiceRenderedID),
render.OutgoingInternetID: theOutgoingInternetNode,
}).Prune()

View File

@@ -4,6 +4,16 @@ import (
"strings"
)
// ParseEndpointID parses endpoint IDs
func ParseEndpointID(id string) (host, ip, port string, ok bool) {
parts := strings.SplitN(id, ":", 4)
if len(parts) != 4 || parts[0] != "endpoint" {
return
}
host, ip, port, ok = parts[1], parts[2], parts[3], true
return
}
// makeID is the generic ID maker
func makeID(prefix string, parts ...string) string {
return strings.Join(append([]string{prefix}, parts...), ":")

View File

@@ -23,9 +23,10 @@ const (
TheInternetID = "theinternet"
IncomingInternetID = "in-" + TheInternetID
OutgoingInternetID = "out-" + TheInternetID
InboundMajor = "Inbound"
OutboundMajor = "Outbound"
RequestsMinor = "Requests"
InboundMajor = "The Internet"
OutboundMajor = "The Internet"
InboundMinor = "Inbound connections"
OutboundMinor = "Outbound connections"
ContainersKey = "containers"
ipsKey = "ips"
@@ -50,19 +51,20 @@ func theInternetNode(m RenderableNode) RenderableNode {
if len(m.Adjacency) > 0 {
node.ID = IncomingInternetID
node.LabelMajor = InboundMajor
node.LabelMinor = RequestsMinor
node.LabelMinor = InboundMinor
} else {
node.ID = OutgoingInternetID
node.LabelMajor = OutboundMajor
node.LabelMinor = RequestsMinor
node.LabelMinor = OutboundMinor
}
return node
}
// MapEndpointIdentity maps an endpoint topology node to a single endpoint
// renderable node. As it is only ever run on endpoint topology nodes, we
// expect that certain keys are present.
func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNodes {
// MapEndpointIdentity remaps endpoints to have an id format consistent
// with render/id.go; no pseudo nodes are introduced in this step, so
// that pseudo nodes introduces later are guaranteed to have endpoints
// as children. This is needed to construct the connection details tables.
func MapEndpointIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
addr, ok := m.Latest.Lookup(endpoint.Addr)
if !ok {
return RenderableNodes{}
@@ -79,46 +81,8 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode
return RenderableNodes{}
}
// Nodes without a hostid are treated as psuedo nodes
if _, ok = m.Latest.Lookup(report.HostNodeID); !ok {
// If the dstNodeAddr is not in a network local to this report, we emit an
// internet node
if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
return RenderableNodes{TheInternetID: theInternetNode(m)}
}
// We are a 'client' pseudo node if the port is in the ephemeral port range.
// Linux uses 32768 to 61000, IANA suggests 49152 to 65535.
if p, err := strconv.Atoi(port); err == nil && len(m.Adjacency) > 0 && p >= 32768 && p < 65535 {
// We only exist if there is something in our adjacency
// Generate a single pseudo node for every (client ip, server ip, server port)
dstNodeID := m.Adjacency[0]
serverIP, serverPort := trySplitAddr(dstNodeID)
outputID := MakePseudoNodeID(addr, serverIP, serverPort)
return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)}
}
// Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port)
outputID := MakePseudoNodeID(addr, port)
if port != "" {
return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr+":"+port, m)}
}
return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)}
}
var (
id = MakeEndpointID(report.ExtractHostID(m.Node), addr, port)
major = fmt.Sprintf("%s:%s", addr, port)
minor = report.ExtractHostID(m.Node)
rank = major
)
pid, pidOK := m.Latest.Lookup(process.PID)
if pidOK {
minor = fmt.Sprintf("%s (%s)", minor, pid)
}
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port)
return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)}
}
// MapProcessIdentity maps a process topology node to a process renderable
@@ -155,10 +119,9 @@ func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
id = MakeContainerID(containerID)
major, _ = GetRenderableContainerName(m.Node)
minor = report.ExtractHostID(m.Node)
rank, _ = m.Latest.Lookup(docker.ImageID)
)
node := NewRenderableNodeWith(id, major, minor, rank, m)
node := NewRenderableNodeWith(id, major, minor, "", m)
node.ControlNode = m.ID
node.Shape = Hexagon
return RenderableNodes{id: node}
@@ -289,10 +252,9 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes
id = MakeAddressID(report.ExtractHostID(m.Node), addr)
major = addr
minor = report.ExtractHostID(m.Node)
rank = major
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, "", m)}
}
// MapHostIdentity maps a host topology node to a host renderable node. As it
@@ -395,7 +357,7 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{}
}
// Propogate the internet pseudo node
// Propagate the internet pseudo node
if strings.HasSuffix(n.ID, TheInternetID) {
return RenderableNodes{n.ID: n}
}
@@ -425,9 +387,45 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
// format for a process, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a process graph to get that info.
func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes {
if n.Pseudo {
return RenderableNodes{n.ID: n}
func MapEndpoint2Process(n RenderableNode, local report.Networks) RenderableNodes {
// Nodes without a hostid are treated as psuedo nodes
if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
var node RenderableNode
addr, ok := n.Latest.Lookup(endpoint.Addr)
if !ok {
return RenderableNodes{}
}
port, ok := n.Latest.Lookup(endpoint.Port)
if !ok {
return RenderableNodes{}
}
if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
// If the dstNodeAddr is not in a network local to this report, we emit an
// internet node
node = theInternetNode(n)
} else if p, err := strconv.Atoi(port); err == nil && len(n.Adjacency) > 0 && p >= 32768 && p < 65535 {
// We are a 'client' pseudo node if the port is in the ephemeral port range.
// Linux uses 32768 to 61000, IANA suggests 49152 to 65535.
// We only exist if there is something in our adjacency
// Generate a single pseudo node for every (client ip, server ip, server port)
_, serverIP, serverPort, _ := ParseEndpointID(n.Adjacency[0])
node = newDerivedPseudoNode(MakePseudoNodeID(addr, serverIP, serverPort), addr, n)
} else if port != "" {
// Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port)
node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr+":"+port, n)
} else {
// Empty port for some reason...
node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr, n)
}
node.Children = node.Children.Add(n)
return RenderableNodes{node.ID: node}
}
pid, ok := n.Node.Latest.Lookup(process.PID)
@@ -438,6 +436,7 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes {
id := MakeProcessID(report.ExtractHostID(n.Node), pid)
node := NewDerivedNode(id, n.WithParents(report.EmptySets))
node.Shape = Square
node.Children = node.Children.Add(n)
return RenderableNodes{id: node}
}
@@ -487,7 +486,7 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
node.Stack = true
}
node.Children = node.Children.Add(n.Node)
node.Children = node.Children.Add(n)
return RenderableNodes{id: node}
}
@@ -513,7 +512,7 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
node.Counters = node.Node.Counters.Add(processesKey, 1)
node.Node.Topology = "process_name"
node.Node.ID = name
node.Children = node.Children.Add(n.Node)
node.Children = node.Children.Add(n)
node.Shape = Square
node.Stack = true
return RenderableNodes{name: node}
@@ -567,7 +566,7 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
result.Node.Counters = result.Node.Counters.Add(ContainersKey, 1)
// Add the container as a child of the new image node
result.Children = result.Children.Add(n.Node)
result.Children = result.Children.Add(n)
result.Node.Topology = "container_image"
result.Node.ID = report.MakeContainerImageNodeID(imageID)
@@ -576,42 +575,6 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
return RenderableNodes{id: result}
}
// MapPod2Service maps pod RenderableNodes to service RenderableNodes.
//
// If this function is given a node without a kubernetes_pod_id
// (including other pseudo nodes), it will produce an "Uncontained"
// pseudo node.
//
// Otherwise, this function will produce a node with the correct ID
// format for a container, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a pod graph to get that info.
func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes {
// Propogate all pseudo nodes
if n.Pseudo {
return RenderableNodes{n.ID: n}
}
// Otherwise, if some some reason the pod doesn't have a service_ids (maybe
// slightly out of sync reports, or its not in a service), just drop it
ids, ok := n.Node.Latest.Lookup(kubernetes.ServiceIDs)
if !ok {
return RenderableNodes{}
}
result := RenderableNodes{}
for _, serviceID := range strings.Fields(ids) {
id := MakeServiceID(serviceID)
n := NewDerivedNode(id, n.WithParents(report.EmptySets))
n.Node.Counters = n.Node.Counters.Add(podsKey, 1)
n.Children = n.Children.Add(n.Node)
n.Shape = Heptagon
n.Stack = true
result[id] = n
}
return result
}
// ImageNameWithoutVersion splits the image name apart, returning the name
// without the version, if possible
func ImageNameWithoutVersion(name string) string {
@@ -671,7 +634,7 @@ func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes {
}
id := MakeHostID(report.ExtractHostID(n.Node))
result := NewDerivedNode(id, n.WithParents(report.EmptySets))
result.Children = result.Children.Add(n.Node)
result.Children = result.Children.Add(n)
result.Shape = Circle
return RenderableNodes{id: result}
}
@@ -717,11 +680,47 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
})
}
result.Children = result.Children.Add(n.Node)
result.Shape = Heptagon
result.Children = result.Children.Add(n)
return RenderableNodes{id: result}
}
// MapPod2Service maps pod RenderableNodes to service RenderableNodes.
//
// If this function is given a node without a kubernetes_pod_id
// (including other pseudo nodes), it will produce an "Uncontained"
// pseudo node.
//
// Otherwise, this function will produce a node with the correct ID
// format for a container, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a pod graph to get that info.
func MapPod2Service(pod RenderableNode, _ report.Networks) RenderableNodes {
// Propogate all pseudo nodes
if pod.Pseudo {
return RenderableNodes{pod.ID: pod}
}
// Otherwise, if some some reason the pod doesn't have a service_ids (maybe
// slightly out of sync reports, or its not in a service), just drop it
ids, ok := pod.Node.Latest.Lookup(kubernetes.ServiceIDs)
if !ok {
return RenderableNodes{}
}
result := RenderableNodes{}
for _, serviceID := range strings.Fields(ids) {
id := MakeServiceID(serviceID)
node := NewDerivedNode(id, pod.WithParents(report.EmptySets))
node.Node.Counters = node.Node.Counters.Add(podsKey, 1)
node.Children = node.Children.Add(pod)
node.Shape = Heptagon
node.Stack = true
result[id] = node
}
return result
}
// MapContainer2Hostname maps container RenderableNodes to 'hostname' renderabled nodes..
func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes {
// Propogate all pseudo nodes
@@ -744,7 +743,7 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes
result.Counters = result.Counters.Add(ContainersKey, 1)
result.Node.Topology = "container_hostname"
result.Node.ID = id
result.Children = result.Children.Add(n.Node)
result.Children = result.Children.Add(n)
result.Shape = Hexagon
result.Stack = true
return RenderableNodes{id: result}
@@ -784,20 +783,3 @@ func MapCountPods(n RenderableNode, _ report.Networks) RenderableNodes {
}
return RenderableNodes{output.ID: output}
}
// trySplitAddr is basically ParseArbitraryNodeID, since its callsites
// (pseudo funcs) just have opaque node IDs and don't know what topology they
// come from. Without changing how pseudo funcs work, we can't make it much
// smarter.
//
// TODO change how pseudofuncs work, and eliminate this helper.
func trySplitAddr(addr string) (string, string) {
fields := strings.SplitN(addr, report.ScopeDelim, 3)
if len(fields) == 3 {
return fields[1], fields[2]
}
if len(fields) == 2 {
return fields[1], ""
}
panic(addr)
}

View File

@@ -8,15 +8,15 @@ import (
// an element of a topology. It should contain information that's relevant
// to rendering a node when there are many nodes visible at once.
type RenderableNode struct {
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Children report.NodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one
ControlNode string `json:"-"` // ID of node from which to show the controls in the UI
Shape string `json:"shape"` // Shape node should be rendered as
Stack bool `json:"stack"` // Should UI render this node as a stack?
ID string `json:"id"` //
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
Rank string `json:"rank"` // to help the layout engine
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Children RenderableNodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one
ControlNode string `json:"control_node"` // ID of node from which to show the controls in the UI
Shape string `json:"shape"` // Shape node should be rendered as
Stack bool `json:"stack"` // Should UI render this node as a stack?
report.EdgeMetadata `json:"metadata"` // Numeric sums
report.Node
@@ -159,7 +159,10 @@ func (rn RenderableNode) Copy() RenderableNode {
func (rn RenderableNode) Prune() RenderableNode {
cp := rn.Copy()
cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...)
cp.Children = report.EmptyNodeSet
cp.Children = MakeRenderableNodeSet()
rn.Children.ForEach(func(n RenderableNode) {
cp.Children = cp.Children.Add(n.Prune())
})
return cp
}

View File

@@ -1,33 +1,35 @@
package report
package render
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"sort"
"github.com/davecgh/go-spew/spew"
"github.com/mndrix/ps"
"github.com/ugorji/go/codec"
"github.com/weaveworks/scope/test/reflect"
)
// NodeSet is a set of nodes keyed on (Topology, ID). Clients must use
// RenderableNodeSet is a set of nodes keyed on (Topology, ID). Clients must use
// the Add method to add nodes
type NodeSet struct {
type RenderableNodeSet struct {
psMap ps.Map
}
// EmptyNodeSet is the empty set of nodes.
var EmptyNodeSet = NodeSet{ps.NewMap()}
// EmptyRenderableNodeSet is the empty set of nodes.
var EmptyRenderableNodeSet = RenderableNodeSet{ps.NewMap()}
// MakeNodeSet makes a new NodeSet with the given nodes.
func MakeNodeSet(nodes ...Node) NodeSet {
return EmptyNodeSet.Add(nodes...)
// MakeRenderableNodeSet makes a new RenderableNodeSet with the given nodes.
func MakeRenderableNodeSet(nodes ...RenderableNode) RenderableNodeSet {
return EmptyRenderableNodeSet.Add(nodes...)
}
// Add adds the nodes to the NodeSet. Add is the only valid way to grow a
// NodeSet. Add returns the NodeSet to enable chaining.
func (n NodeSet) Add(nodes ...Node) NodeSet {
// Add adds the nodes to the RenderableNodeSet. Add is the only valid way to grow a
// RenderableNodeSet. Add returns the RenderableNodeSet to enable chaining.
func (n RenderableNodeSet) Add(nodes ...RenderableNode) RenderableNodeSet {
result := n.psMap
if result == nil {
result = ps.NewMap()
@@ -35,11 +37,11 @@ func (n NodeSet) Add(nodes ...Node) NodeSet {
for _, node := range nodes {
result = result.Set(fmt.Sprintf("%s|%s", node.Topology, node.ID), node)
}
return NodeSet{result}
return RenderableNodeSet{result}
}
// Merge combines the two NodeSets and returns a new result.
func (n NodeSet) Merge(other NodeSet) NodeSet {
// Merge combines the two RenderableNodeSets and returns a new result.
func (n RenderableNodeSet) Merge(other RenderableNodeSet) RenderableNodeSet {
nSize, otherSize := n.Size(), other.Size()
if nSize == 0 {
return other
@@ -54,22 +56,22 @@ func (n NodeSet) Merge(other NodeSet) NodeSet {
iter.ForEach(func(key string, otherVal interface{}) {
result = result.Set(key, otherVal)
})
return NodeSet{result}
return RenderableNodeSet{result}
}
// Lookup the node 'key'
func (n NodeSet) Lookup(key string) (Node, bool) {
func (n RenderableNodeSet) Lookup(key string) (RenderableNode, bool) {
if n.psMap != nil {
value, ok := n.psMap.Lookup(key)
if ok {
return value.(Node), true
return value.(RenderableNode), true
}
}
return Node{}, false
return RenderableNode{}, false
}
// Keys is a list of all the keys in this set.
func (n NodeSet) Keys() []string {
func (n RenderableNodeSet) Keys() []string {
if n.psMap == nil {
return nil
}
@@ -79,7 +81,7 @@ func (n NodeSet) Keys() []string {
}
// Size is the number of nodes in the set
func (n NodeSet) Size() int {
func (n RenderableNodeSet) Size() int {
if n.psMap == nil {
return 0
}
@@ -88,23 +90,23 @@ func (n NodeSet) Size() int {
// ForEach executes f for each node in the set. Nodes are traversed in sorted
// order.
func (n NodeSet) ForEach(f func(Node)) {
func (n RenderableNodeSet) ForEach(f func(RenderableNode)) {
for _, key := range n.Keys() {
if val, ok := n.psMap.Lookup(key); ok {
f(val.(Node))
f(val.(RenderableNode))
}
}
}
// Copy is a noop
func (n NodeSet) Copy() NodeSet {
func (n RenderableNodeSet) Copy() RenderableNodeSet {
return n
}
func (n NodeSet) String() string {
func (n RenderableNodeSet) String() string {
keys := []string{}
if n.psMap == nil {
n = EmptyNodeSet
n = EmptyRenderableNodeSet
}
psMap := n.psMap
if psMap == nil {
@@ -118,15 +120,15 @@ func (n NodeSet) String() string {
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
fmt.Fprintf(buf, "%s: %s, ", key, spew.Sdump(val))
}
fmt.Fprintf(buf, "}\n")
fmt.Fprintf(buf, "}")
return buf.String()
}
// DeepEqual tests equality with other NodeSets
func (n NodeSet) DeepEqual(i interface{}) bool {
d, ok := i.(NodeSet)
// DeepEqual tests equality with other RenderableNodeSets
func (n RenderableNodeSet) DeepEqual(i interface{}) bool {
d, ok := i.(RenderableNodeSet)
if !ok {
return false
}
@@ -149,20 +151,20 @@ func (n NodeSet) DeepEqual(i interface{}) bool {
return equal
}
func (n NodeSet) toIntermediate() []Node {
intermediate := make([]Node, 0, n.Size())
n.ForEach(func(node Node) {
func (n RenderableNodeSet) toIntermediate() []RenderableNode {
intermediate := make([]RenderableNode, 0, n.Size())
n.ForEach(func(node RenderableNode) {
intermediate = append(intermediate, node)
})
return intermediate
}
func (n NodeSet) fromIntermediate(nodes []Node) NodeSet {
return MakeNodeSet(nodes...)
func (n RenderableNodeSet) fromIntermediate(nodes []RenderableNode) RenderableNodeSet {
return MakeRenderableNodeSet(nodes...)
}
// CodecEncodeSelf implements codec.Selfer
func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
func (n *RenderableNodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
if n.psMap != nil {
encoder.Encode(n.toIntermediate())
} else {
@@ -171,37 +173,37 @@ func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) {
}
// CodecDecodeSelf implements codec.Selfer
func (n *NodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
in := []Node{}
func (n *RenderableNodeSet) CodecDecodeSelf(decoder *codec.Decoder) {
in := []RenderableNode{}
if err := decoder.Decode(&in); err != nil {
return
}
*n = NodeSet{}.fromIntermediate(in)
*n = RenderableNodeSet{}.fromIntermediate(in)
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
func (NodeSet) MarshalJSON() ([]byte, error) {
func (RenderableNodeSet) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
func (*NodeSet) UnmarshalJSON(b []byte) error {
func (*RenderableNodeSet) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}
// GobEncode implements gob.Marshaller
func (n NodeSet) GobEncode() ([]byte, error) {
func (n RenderableNodeSet) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(n.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (n *NodeSet) GobDecode(input []byte) error {
in := []Node{}
func (n *RenderableNodeSet) GobDecode(input []byte) error {
in := []RenderableNode{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*n = NodeSet{}.fromIntermediate(in)
*n = RenderableNodeSet{}.fromIntermediate(in)
return nil
}

View File

@@ -0,0 +1,272 @@
package render_test
import (
"fmt"
"testing"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
var benchmarkResult render.RenderableNodeSet
type nodeSpec struct {
topology string
id string
}
func renderableNode(n report.Node) render.RenderableNode {
node := render.NewRenderableNode(n.ID)
node.Topology = n.Topology
return node
}
func TestMakeRenderableNodeSet(t *testing.T) {
for _, testcase := range []struct {
inputs []nodeSpec
wants []nodeSpec
}{
{inputs: nil, wants: nil},
{inputs: []nodeSpec{}, wants: []nodeSpec{}},
{
inputs: []nodeSpec{{"", "a"}},
wants: []nodeSpec{{"", "a"}},
},
{
inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}},
wants: []nodeSpec{{"", "a"}, {"1", "a"}},
},
{
inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}},
wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}},
},
{
inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}},
wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}},
},
} {
var (
inputs []render.RenderableNode
wants []render.RenderableNode
)
for _, spec := range testcase.inputs {
node := render.NewRenderableNode(spec.id)
node.Topology = spec.topology
inputs = append(inputs, node)
}
for _, spec := range testcase.wants {
node := render.NewRenderableNode(spec.id)
node.Topology = spec.topology
wants = append(wants, node)
}
if want, have := render.MakeRenderableNodeSet(wants...), render.MakeRenderableNodeSet(inputs...); !reflect.DeepEqual(want, have) {
t.Errorf("%#v: want %#v, have %#v", inputs, wants, have)
}
}
}
func BenchmarkMakeRenderableNodeSet(b *testing.B) {
nodes := []render.RenderableNode{}
for i := 1000; i >= 0; i-- {
node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
})
rn := render.NewRenderableNode(node.ID)
rn.Node = node
nodes = append(nodes, rn)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = render.MakeRenderableNodeSet(nodes...)
}
}
func TestRenderableNodeSetAdd(t *testing.T) {
for _, testcase := range []struct {
input render.RenderableNodeSet
nodes []render.RenderableNode
want render.RenderableNodeSet
}{
{
input: render.RenderableNodeSet{},
nodes: []render.RenderableNode{},
want: render.RenderableNodeSet{},
},
{
input: render.EmptyRenderableNodeSet,
nodes: []render.RenderableNode{},
want: render.EmptyRenderableNodeSet,
},
{
input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
nodes: []render.RenderableNode{},
want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
},
{
input: render.EmptyRenderableNodeSet,
nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))},
want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
},
{
input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))},
want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
},
{
input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("b"))),
nodes: []render.RenderableNode{
renderableNode(report.MakeNode().WithID("a")),
renderableNode(report.MakeNode().WithID("b")),
},
want: render.MakeRenderableNodeSet(
renderableNode(report.MakeNode().WithID("a")),
renderableNode(report.MakeNode().WithID("b")),
),
},
{
input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))),
nodes: []render.RenderableNode{
renderableNode(report.MakeNode().WithID("c")),
renderableNode(report.MakeNode().WithID("b")),
},
want: render.MakeRenderableNodeSet(
renderableNode(report.MakeNode().WithID("a")),
renderableNode(report.MakeNode().WithID("b")),
renderableNode(report.MakeNode().WithID("c")),
),
},
{
input: render.MakeRenderableNodeSet(
renderableNode(report.MakeNode().WithID("a")),
renderableNode(report.MakeNode().WithID("c")),
),
nodes: []render.RenderableNode{
renderableNode(report.MakeNode().WithID("b")),
renderableNode(report.MakeNode().WithID("b")),
renderableNode(report.MakeNode().WithID("b")),
},
want: render.MakeRenderableNodeSet(
renderableNode(report.MakeNode().WithID("a")),
renderableNode(report.MakeNode().WithID("b")),
renderableNode(report.MakeNode().WithID("c")),
),
},
} {
originalLen := testcase.input.Size()
if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) {
t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have)
}
if testcase.input.Size() != originalLen {
t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes)
}
}
}
func BenchmarkRenderableNodeSetAdd(b *testing.B) {
n := render.EmptyRenderableNodeSet
for i := 0; i < 600; i++ {
n = n.Add(
renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
})),
)
}
node := renderableNode(report.MakeNode().WithID("401.5").WithLatests(map[string]string{
"a": "1",
"b": "2",
}))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = n.Add(node)
}
}
func TestRenderableNodeSetMerge(t *testing.T) {
for _, testcase := range []struct {
input render.RenderableNodeSet
other render.RenderableNodeSet
want render.RenderableNodeSet
}{
{input: render.RenderableNodeSet{}, other: render.RenderableNodeSet{}, want: render.RenderableNodeSet{}},
{input: render.EmptyRenderableNodeSet, other: render.EmptyRenderableNodeSet, want: render.EmptyRenderableNodeSet},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
other: render.EmptyRenderableNodeSet,
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
},
{
input: render.EmptyRenderableNodeSet,
other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
other: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("c")),
other: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b"), render.NewRenderableNode("c")),
},
{
input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")),
other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")),
want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")),
},
} {
originalLen := testcase.input.Size()
if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) {
t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have)
}
if testcase.input.Size() != originalLen {
t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other)
}
}
}
func BenchmarkRenderableNodeSetMerge(b *testing.B) {
n, other := render.RenderableNodeSet{}, render.RenderableNodeSet{}
for i := 0; i < 600; i++ {
n = n.Add(
renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
})),
)
}
for i := 400; i < 1000; i++ {
other = other.Add(
renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"c": "1",
"d": "2",
})),
)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = n.Merge(other)
}
}

View File

@@ -37,7 +37,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "",
Pseudo: false,
Node: report.MakeNode().WithAdjacent("a1"),
Children: report.MakeNodeSet(report.MakeNode().WithID("child1")),
Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1")),
}
node2 := render.RenderableNode{
ID: "foo",
@@ -46,7 +46,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "rank",
Pseudo: false,
Node: report.MakeNode().WithAdjacent("a2"),
Children: report.MakeNodeSet(report.MakeNode().WithID("child2")),
Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child2")),
}
want := render.RenderableNode{
ID: "foo",
@@ -55,7 +55,7 @@ func TestMergeRenderableNode(t *testing.T) {
Rank: "rank",
Pseudo: false,
Node: report.MakeNode().WithID("foo").WithAdjacent("a1").WithAdjacent("a2"),
Children: report.MakeNodeSet(report.MakeNode().WithID("child1"), report.MakeNode().WithID("child2")),
Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1"), render.NewRenderableNode("child2")),
EdgeMetadata: report.EdgeMetadata{},
}.Prune()
have := node1.Merge(node2).Prune()

View File

@@ -73,7 +73,7 @@ var (
render.IncomingInternetID: {
ID: render.IncomingInternetID,
LabelMajor: render.InboundMajor,
LabelMinor: render.RequestsMinor,
LabelMinor: render.InboundMinor,
Pseudo: true,
Shape: "cloud",
Node: report.MakeNode().WithAdjacent(render.MakeContainerID(containerID)),

View File

@@ -1,7 +1,6 @@
package render_test
import (
"reflect"
"testing"
"github.com/weaveworks/scope/probe/docker"
@@ -11,8 +10,17 @@ import (
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
"github.com/weaveworks/scope/test/reflect"
)
func TestEndpointRenderer(t *testing.T) {
have := render.EndpointRenderer.Render(fixture.Report).Prune()
want := expected.RenderedEndpoints
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestProcessRenderer(t *testing.T) {
have := render.ProcessRenderer.Render(fixture.Report).Prune()
want := expected.RenderedProcesses
@@ -30,7 +38,7 @@ func TestProcessNameRenderer(t *testing.T) {
}
func TestContainerRenderer(t *testing.T) {
have := (render.ContainerWithImageNameRenderer.Render(fixture.Report)).Prune()
have := (render.ContainerRenderer.Render(fixture.Report)).Prune()
want := expected.RenderedContainers
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -44,9 +52,9 @@ func TestContainerFilterRenderer(t *testing.T) {
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.LabelPrefix + "works.weave.role": "system",
})
have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune()
have := render.FilterSystem(render.ContainerRenderer).Render(input).Prune()
want := expected.RenderedContainers.Copy()
delete(want, expected.ClientContainerRenderedID)
delete(want, expected.ClientContainerID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -71,23 +79,17 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
}
}
func TestContainerFilterRendererImageName(t *testing.T) {
// Test nodes are filtered by image name as well.
input := fixture.Report.Copy()
input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID] = input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID].WithLatests(map[string]string{
docker.ImageName: "beta.gcr.io/google_containers/pause",
})
have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune()
want := expected.RenderedContainers.Copy()
delete(want, expected.ClientContainerRenderedID)
func TestContainerImageRenderer(t *testing.T) {
have := render.ContainerImageRenderer.Render(fixture.Report).Prune()
want := expected.RenderedContainerImages
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerImageRenderer(t *testing.T) {
have := render.ContainerImageRenderer.Render(fixture.Report).Prune()
want := expected.RenderedContainerImages
func TestAddressRenderer(t *testing.T) {
have := render.AddressRenderer.Render(fixture.Report).Prune()
want := expected.RenderedAddresses
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -124,7 +126,7 @@ func TestPodFilterRenderer(t *testing.T) {
have := render.FilterSystem(render.PodRenderer).Render(input).Prune()
want := expected.RenderedPods.Copy()
delete(want, expected.ClientPodRenderedID)
delete(want, expected.ClientContainerRenderedID)
delete(want, expected.ClientContainerID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}

View File

@@ -103,7 +103,7 @@ func (c Counters) String() string {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %d, ", key, val)
}
fmt.Fprintf(buf, "}\n")
fmt.Fprintf(buf, "}")
return buf.String()
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"sort"
"strconv"
"github.com/mndrix/ps"
"github.com/ugorji/go/codec"
@@ -123,7 +124,7 @@ func (c EdgeMetadatas) String() string {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
fmt.Fprintf(buf, "}\n")
fmt.Fprintf(buf, "}")
return buf.String()
}
@@ -219,6 +220,28 @@ type EdgeMetadata struct {
IngressByteCount *uint64 `json:"ingress_byte_count,omitempty"` // Transport layer
}
// String returns a string representation of this EdgeMetadata
// Helps with our use of Spew and diff.
func (e EdgeMetadata) String() string {
f := func(i *uint64) string {
if i == nil {
return "nil"
}
return strconv.FormatUint(*i, 10)
}
return fmt.Sprintf(`{
EgressPacketCount: %v,
IngressPacketCount: %v,
EgressByteCount: %v,
IngressByteCount: %v,
}`,
f(e.EgressPacketCount),
f(e.IngressPacketCount),
f(e.EgressByteCount),
f(e.IngressByteCount))
}
// Copy returns a value copy of the EdgeMetadata.
func (e EdgeMetadata) Copy() EdgeMetadata {
return EdgeMetadata{

View File

@@ -1,7 +1,5 @@
package report
import "sort"
// IDList is a list of string IDs, which are always sorted and unique.
type IDList StringSet
@@ -35,6 +33,10 @@ func (a IDList) Merge(b IDList) IDList {
// Contains returns true if id is in the list.
func (a IDList) Contains(id string) bool {
i := sort.Search(len(a), func(i int) bool { return a[i] >= id })
return i < len(a) && a[i] == id
return StringSet(a).Contains(id)
}
// Intersection returns the intersection of a and b
func (a IDList) Intersection(b IDList) IDList {
return IDList(StringSet(a).Intersection(StringSet(b)))
}

View File

@@ -137,7 +137,7 @@ func (m LatestMap) String() string {
val, _ := m.Map.Lookup(key)
fmt.Fprintf(buf, "%s: %s,\n", key, val)
}
fmt.Fprintf(buf, "}\n")
fmt.Fprintf(buf, "}")
return buf.String()
}

View File

@@ -1,231 +0,0 @@
package report_test
import (
"fmt"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
var benchmarkResult report.NodeSet
type nodeSpec struct {
topology string
id string
}
func TestMakeNodeSet(t *testing.T) {
for _, testcase := range []struct {
inputs []nodeSpec
wants []nodeSpec
}{
{inputs: nil, wants: nil},
{inputs: []nodeSpec{}, wants: []nodeSpec{}},
{
inputs: []nodeSpec{{"", "a"}},
wants: []nodeSpec{{"", "a"}},
},
{
inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}},
wants: []nodeSpec{{"", "a"}, {"1", "a"}},
},
{
inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}},
wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}},
},
{
inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}},
wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}},
},
} {
var (
inputs []report.Node
wants []report.Node
)
for _, spec := range testcase.inputs {
inputs = append(inputs, report.MakeNode().WithTopology(spec.topology).WithID(spec.id))
}
for _, spec := range testcase.wants {
wants = append(wants, report.MakeNode().WithTopology(spec.topology).WithID(spec.id))
}
if want, have := report.MakeNodeSet(wants...), report.MakeNodeSet(inputs...); !reflect.DeepEqual(want, have) {
t.Errorf("%#v: want %#v, have %#v", inputs, wants, have)
}
}
}
func BenchmarkMakeNodeSet(b *testing.B) {
nodes := []report.Node{}
for i := 1000; i >= 0; i-- {
node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
})
nodes = append(nodes, node)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = report.MakeNodeSet(nodes...)
}
}
func TestNodeSetAdd(t *testing.T) {
for _, testcase := range []struct {
input report.NodeSet
nodes []report.Node
want report.NodeSet
}{
{input: report.NodeSet{}, nodes: []report.Node{}, want: report.NodeSet{}},
{
input: report.EmptyNodeSet,
nodes: []report.Node{},
want: report.EmptyNodeSet,
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
nodes: []report.Node{},
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.EmptyNodeSet,
nodes: []report.Node{report.MakeNode().WithID("a")},
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
nodes: []report.Node{report.MakeNode().WithID("a")},
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("b")),
nodes: []report.Node{report.MakeNode().WithID("a"), report.MakeNode().WithID("b")},
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
nodes: []report.Node{report.MakeNode().WithID("c"), report.MakeNode().WithID("b")},
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")),
nodes: []report.Node{report.MakeNode().WithID("b"), report.MakeNode().WithID("b"), report.MakeNode().WithID("b")},
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
},
} {
originalLen := testcase.input.Size()
if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) {
t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have)
}
if testcase.input.Size() != originalLen {
t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes)
}
}
}
func BenchmarkNodeSetAdd(b *testing.B) {
n := report.EmptyNodeSet
for i := 0; i < 600; i++ {
n = n.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
}),
)
}
node := report.MakeNode().WithID("401.5").WithLatests(map[string]string{
"a": "1",
"b": "2",
})
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = n.Add(node)
}
}
func TestNodeSetMerge(t *testing.T) {
for _, testcase := range []struct {
input report.NodeSet
other report.NodeSet
want report.NodeSet
}{
{input: report.NodeSet{}, other: report.NodeSet{}, want: report.NodeSet{}},
{input: report.EmptyNodeSet, other: report.EmptyNodeSet, want: report.EmptyNodeSet},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
other: report.EmptyNodeSet,
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.EmptyNodeSet,
other: report.MakeNodeSet(report.MakeNode().WithID("a")),
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
other: report.MakeNodeSet(report.MakeNode().WithID("b")),
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("b")),
other: report.MakeNodeSet(report.MakeNode().WithID("a")),
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a")),
other: report.MakeNodeSet(report.MakeNode().WithID("a")),
want: report.MakeNodeSet(report.MakeNode().WithID("a")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")),
other: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")),
},
{
input: report.MakeNodeSet(report.MakeNode().WithID("b")),
other: report.MakeNodeSet(report.MakeNode().WithID("a")),
want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")),
},
} {
originalLen := testcase.input.Size()
if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) {
t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have)
}
if testcase.input.Size() != originalLen {
t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other)
}
}
}
func BenchmarkNodeSetMerge(b *testing.B) {
n, other := report.NodeSet{}, report.NodeSet{}
for i := 0; i < 600; i++ {
n = n.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
}),
)
}
for i := 400; i < 1000; i++ {
other = other.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"c": "1",
"d": "2",
}),
)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkResult = n.Merge(other)
}
}

View File

@@ -114,7 +114,7 @@ func (s Sets) String() string {
val, _ := s.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
fmt.Fprintf(buf, "}\n")
fmt.Fprintf(buf, "}")
return buf.String()
}

View File

@@ -35,6 +35,22 @@ func (s StringSet) Contains(str string) bool {
return i < len(s) && s[i] == str
}
// Intersection returns the intersections of a and b
func (s StringSet) Intersection(b StringSet) StringSet {
result, i, j := EmptyStringSet, 0, 0
for i < len(s) && j < len(b) {
if s[i] == b[j] {
result = result.Add(s[i])
}
if s[i] < b[j] {
i++
} else {
j++
}
}
return result
}
// Add adds the strings to the StringSet. Add is the only valid way to grow a
// StringSet. Add returns the StringSet to enable chaining.
func (s StringSet) Add(strs ...string) StringSet {