Merge branch 'master' of github.com:weaveworks/scope

This commit is contained in:
Paul Bellamy
2016-01-26 10:03:55 +00:00
75 changed files with 2850 additions and 1358 deletions

View File

@@ -217,23 +217,38 @@ Then, run the local build via
Scope has a collection of built in debugging tools to aid Scope delevopers.
- To have the app or probe dump their goroutine stacks, run:
- To have the Scope App or Scope Probe dump their goroutine stacks, run:
```
pkill -SIGQUIT scope-(app|probe)
docker logs weavescope
```
- The probe is instrumented with various counters and timers. To have it dump
- The Scope Probe is instrumented with various counters and timers. To have it dump
those values, run:
```
pkill -SIGUSR1 scope-probe
docker logs weavescope
```
- The app and probe both include golang's pprof integration for gathering CPU
and memory profiles. To use these with the probe, you must launch Scope with
the following arguments `scope launch --probe.http.listen :4041`. You can
then collect profiles in the usual way:
- Both the Scope App and the Scope Probe offer
[http endpoints with profiling information](https://golang.org/pkg/net/http/pprof/).
These cover things such as CPU usage and memory consumption:
* The Scope App enables its http profiling endpoints by default, which
are accessible on the same port the Scope UI is served (4040).
* The Scope Probe doesn't enable its profiling endpoints by default.
To enable them, you must launch Scope with `--probe.http.listen addr:port`.
For instance, launching scope with `scope launch --probe.http.listen :4041`, will
allow you access the Scope Probe's profiling endpoints on port 4041.
Then, you can collect profiles in the usual way. For instance:
* To collect the Memory profile of the Scope App:
```
go tool pprof http://localhost:4040/debug/pprof/heap
```
go tool pprof http://localhost:(4040|4041)/debug/pprof/profile
* To collect the CPU profile of the Scope Probe:
```
go tool pprof http://localhost:4041/debug/pprof/profile
```

View File

@@ -159,11 +159,12 @@ export function hitEsc() {
type: ActionTypes.CLICK_CLOSE_TERMINAL,
pipeId: controlPipe.id
});
updateRoute();
// Dont deselect node on ESC if there is a controlPipe (keep terminal open)
} else if (AppStore.getSelectedNodeId() && !controlPipe) {
} else if (AppStore.getTopCardNodeId() && !controlPipe) {
AppDispatcher.dispatch({type: ActionTypes.DESELECT_NODE});
updateRoute();
}
updateRoute();
}
export function leaveEdge(edgeId) {
@@ -242,7 +243,7 @@ export function receiveControlPipeFromParams(pipeId, rawTty) {
}
export function receiveControlPipe(pipeId, nodeId, rawTty) {
if (nodeId !== AppStore.getSelectedNodeId()) {
if (nodeId !== AppStore.getTopCardNodeId()) {
log('Node was deselected before we could set up control!');
deletePipe(pipeId);
return;

View File

@@ -87,7 +87,7 @@ export default class App extends React.Component {
{showingTerminal && <EmbeddedTerminal
pipe={this.state.controlPipe}
nodeId={this.state.controlPipe.nodeId}
nodes={this.state.nodes} />}
details={this.state.nodeDetails} />}
<div className="header">
<Logo />

View File

@@ -3,18 +3,19 @@ import React from 'react';
import { getNodeColor, getNodeColorDark } from '../utils/color-utils';
import Terminal from './terminal';
export default function EmeddedTerminal({pipe, nodeId, nodes}) {
const node = nodes.get(nodeId);
const titleBarColor = node && getNodeColorDark(node.get('rank'), node.get('label_major'));
const statusBarColor = node && getNodeColor(node.get('rank'), node.get('label_major'));
const title = node && node.get('label_major');
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;
// React unmount/remounts when key changes, this is important for cleaning up
// the term.js and creating a new one for the new pipe.
return (
<div className="terminal-embedded">
<Terminal key={pipe.id} pipe={pipe} titleBarColor={titleBarColor}
statusBarColor={statusBarColor} title={title} />
statusBarColor={statusBarColor} title={title} />
</div>
);
}

View File

@@ -239,6 +239,10 @@ export class AppStore extends Store {
}).toJS();
}
getTopCardNodeId() {
return nodeDetails.last().id;
}
getNodes() {
return nodes;
}
@@ -551,7 +555,7 @@ export class AppStore extends Store {
selectedNodeId = payload.state.selectedNodeId;
if (payload.state.controlPipe) {
controlPipes = makeOrderedMap({
[payload.state.controlPipe.pipeId]:
[payload.state.controlPipe.id]:
makeOrderedMap(payload.state.controlPipe)
});
} else {

View File

@@ -1,6 +1,6 @@
.PHONY: all test clean
DIRS=$(shell find . -maxdepth 2 -name *.go -printf "%h\n" | sort -u)
DIRS=$(shell find . -maxdepth 2 -name *.go | xargs -n1 dirname | sort -u)
TARGETS=$(join $(patsubst %,%/,$(DIRS)),$(patsubst ./%,%,$(DIRS)))
BUILD_IN_CONTAINER=true
RM=--rm

View File

@@ -90,14 +90,14 @@ func demoReport(nodeCount int) report.Report {
)
// Endpoint topology
r.Endpoint = r.Endpoint.AddNode(srcPortID, report.MakeNode().WithMetadata(map[string]string{
r.Endpoint = r.Endpoint.AddNode(srcPortID, report.MakeNode().WithLatests(map[string]string{
process.PID: "4000",
"name": c.srcProc,
"domain": "node-" + src,
}).WithEdge(dstPortID, report.EdgeMetadata{
MaxConnCountTCP: newu64(uint64(rand.Intn(100) + 10)),
}))
r.Endpoint = r.Endpoint.AddNode(dstPortID, report.MakeNode().WithMetadata(map[string]string{
r.Endpoint = r.Endpoint.AddNode(dstPortID, report.MakeNode().WithLatests(map[string]string{
process.PID: "4000",
"name": c.dstProc,
"domain": "node-" + dst,
@@ -106,10 +106,10 @@ func demoReport(nodeCount int) report.Report {
}))
// Address topology
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithMetadata(map[string]string{
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithLatests(map[string]string{
docker.Name: src,
}).WithAdjacent(dstAddressID))
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithMetadata(map[string]string{
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithLatests(map[string]string{
docker.Name: dst,
}).WithAdjacent(srcAddressID))

View File

@@ -64,14 +64,14 @@ func DemoReport(nodeCount int) report.Report {
)
// Endpoint topology
r.Endpoint = r.Endpoint.AddNode(srcPortID, report.MakeNode().WithMetadata(map[string]string{
r.Endpoint = r.Endpoint.AddNode(srcPortID, report.MakeNode().WithLatests(map[string]string{
"pid": "4000",
"name": c.srcProc,
"domain": "node-" + src,
}).WithEdge(dstPortID, report.EdgeMetadata{
MaxConnCountTCP: newu64(uint64(rand.Intn(100) + 10)),
}))
r.Endpoint = r.Endpoint.AddNode(dstPortID, report.MakeNode().WithMetadata(map[string]string{
r.Endpoint = r.Endpoint.AddNode(dstPortID, report.MakeNode().WithLatests(map[string]string{
"pid": "4000",
"name": c.dstProc,
"domain": "node-" + dst,
@@ -80,10 +80,10 @@ func DemoReport(nodeCount int) report.Report {
}))
// Address topology
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithMetadata(map[string]string{
r.Address = r.Address.AddNode(srcAddressID, report.MakeNode().WithLatests(map[string]string{
"name": src,
}).WithAdjacent(dstAddressID))
r.Address = r.Address.AddNode(dstAddressID, report.MakeNode().WithMetadata(map[string]string{
r.Address = r.Address.AddNode(dstAddressID, report.MakeNode().WithLatests(map[string]string{
"name": dst,
}).WithAdjacent(srcAddressID))

View File

@@ -158,7 +158,7 @@ func interpolateCounts(r report.Report) {
factor := 1.0 / rate
for _, topology := range r.Topologies() {
for _, nmd := range topology.Nodes {
for _, emd := range nmd.Edges {
nmd.Edges.ForEach(func(_ string, emd report.EdgeMetadata) {
if emd.EgressPacketCount != nil {
*emd.EgressPacketCount = uint64(float64(*emd.EgressPacketCount) * factor)
}
@@ -171,7 +171,7 @@ func interpolateCounts(r report.Report) {
if emd.IngressByteCount != nil {
*emd.IngressByteCount = uint64(float64(*emd.IngressByteCount) * factor)
}
}
})
}
}
}
@@ -306,7 +306,8 @@ func (s *Sniffer) Merge(p Packet, rpt *report.Report) {
rpt.Address = addAdjacency(rpt.Address, srcNodeID, dstNodeID)
emd := rpt.Address.Nodes[srcNodeID].Edges[dstNodeID]
node := rpt.Address.Nodes[srcNodeID]
emd, _ := node.Edges.Lookup(dstNodeID)
if egress {
if emd.EgressPacketCount == nil {
emd.EgressPacketCount = new(uint64)
@@ -326,7 +327,7 @@ func (s *Sniffer) Merge(p Packet, rpt *report.Report) {
}
*emd.IngressByteCount += uint64(p.Network)
}
rpt.Address.Nodes[srcNodeID].Edges[dstNodeID] = emd
rpt.Address.Nodes[srcNodeID] = node.WithEdge(dstNodeID, emd)
}
// If we have ports, we can add to the endpoint topology, too.
@@ -338,7 +339,8 @@ func (s *Sniffer) Merge(p Packet, rpt *report.Report) {
rpt.Endpoint = addAdjacency(rpt.Endpoint, srcNodeID, dstNodeID)
emd := rpt.Endpoint.Nodes[srcNodeID].Edges[dstNodeID]
node := rpt.Endpoint.Nodes[srcNodeID]
emd, _ := node.Edges.Lookup(dstNodeID)
if egress {
if emd.EgressPacketCount == nil {
emd.EgressPacketCount = new(uint64)
@@ -358,6 +360,6 @@ func (s *Sniffer) Merge(p Packet, rpt *report.Report) {
}
*emd.IngressByteCount += uint64(p.Transport)
}
rpt.Endpoint.Nodes[srcNodeID].Edges[dstNodeID] = emd
rpt.Endpoint.Nodes[srcNodeID] = node.WithEdge(dstNodeID, emd)
}
}

View File

@@ -325,17 +325,17 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "),
ImageID: c.container.Image,
ContainerHostname: c.Hostname(),
}).WithSets(report.Sets{
ContainerPorts: c.ports(localAddrs),
ContainerIPs: report.MakeStringSet(ips...),
ContainerIPsWithScopes: report.MakeStringSet(ipsWithScopes...),
}).WithLatest(
}).WithSets(report.EmptySets.
Add(ContainerPorts, c.ports(localAddrs)).
Add(ContainerIPs, report.MakeStringSet(ips...)).
Add(ContainerIPsWithScopes, report.MakeStringSet(ipsWithScopes...)),
).WithLatest(
ContainerState, mtime.Now(), state,
).WithMetrics(
c.metrics(),
).WithParents(report.Sets{
report.ContainerImage: report.MakeStringSet(report.MakeContainerImageNodeID(c.container.Image)),
})
).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.container.Image))),
)
if c.container.State.Paused {
result = result.WithControls(UnpauseContainer)
@@ -347,13 +347,13 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
result = result.WithControls(StartContainer)
}
AddLabels(result, c.container.Config.Labels)
result = AddLabels(result, c.container.Config.Labels)
if c.latestStats == nil {
return result
}
result = result.WithMetadata(map[string]string{
result = result.WithLatests(map[string]string{
MemoryMaxUsage: strconv.FormatUint(c.latestStats.MemoryStats.MaxUsage, 10),
MemoryUsage: strconv.FormatUint(c.latestStats.MemoryStats.Usage, 10),
MemoryFailcnt: strconv.FormatUint(c.latestStats.MemoryStats.Failcnt, 10),
@@ -370,11 +370,13 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node {
// ExtractContainerIPs returns the list of container IPs given a Node from the Container topology.
func ExtractContainerIPs(nmd report.Node) []string {
return []string(nmd.Sets[ContainerIPs])
v, _ := nmd.Sets.Lookup(ContainerIPs)
return []string(v)
}
// ExtractContainerIPsWithScopes returns the list of container IPs, prepended
// with scopes, given a Node from the Container topology.
func ExtractContainerIPsWithScopes(nmd report.Node) []string {
return []string(nmd.Sets[ContainerIPsWithScopes])
v, _ := nmd.Sets.Lookup(ContainerIPsWithScopes)
return []string(v)
}

View File

@@ -8,7 +8,6 @@ import (
"log"
"net"
"net/http"
"reflect"
"testing"
"time"
@@ -18,6 +17,7 @@ import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
type mockConnection struct {
@@ -71,7 +71,7 @@ func TestContainer(t *testing.T) {
}
// Now see if we go them
want := report.MakeNode().WithMetadata(map[string]string{
want := report.MakeNode().WithLatests(map[string]string{
"docker_container_command": " ",
"docker_container_created": "01 Jan 01 00:00 UTC",
"docker_container_id": "ping",
@@ -80,11 +80,11 @@ func TestContainer(t *testing.T) {
"docker_label_foo1": "bar1",
"docker_label_foo2": "bar2",
"docker_memory_usage": "12345",
}).WithSets(report.Sets{
"docker_container_ports": report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp"),
"docker_container_ips": report.MakeStringSet("1.2.3.4"),
"docker_container_ips_with_scopes": report.MakeStringSet("scope;1.2.3.4"),
}).WithControls(
}).WithSets(report.EmptySets.
Add("docker_container_ports", report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp")).
Add("docker_container_ips", report.MakeStringSet("1.2.3.4")).
Add("docker_container_ips_with_scopes", report.MakeStringSet("scope;1.2.3.4")),
).WithControls(
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
docker.AttachContainer, docker.ExecContainer,
).WithLatest(
@@ -92,16 +92,17 @@ func TestContainer(t *testing.T) {
).WithMetrics(report.Metrics{
"docker_cpu_total_usage": report.MakeMetric(),
"docker_memory_usage": report.MakeMetric().Add(now, 12345),
}).WithParents(report.Sets{
report.ContainerImage: report.MakeStringSet(report.MakeContainerImageNodeID("baz")),
})
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
)
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
node := c.GetNode("scope", []net.IP{})
for k, v := range node.Metadata {
node.Latest.ForEach(func(k, v string) {
if v == "0" || v == "" {
delete(node.Metadata, k)
node.Latest = node.Latest.Delete(k)
}
}
})
return node
})

View File

@@ -159,3 +159,13 @@ func (r *registry) registerControls() {
controls.Register(AttachContainer, captureContainerID(r.attachContainer))
controls.Register(ExecContainer, captureContainerID(r.execContainer))
}
func (r *registry) deregisterControls() {
controls.Rm(StopContainer)
controls.Rm(StartContainer)
controls.Rm(RestartContainer)
controls.Rm(PauseContainer)
controls.Rm(UnpauseContainer)
controls.Rm(AttachContainer)
controls.Rm(ExecContainer)
}

View File

@@ -79,7 +79,7 @@ func TestPipes(t *testing.T) {
RawTTY: true,
}
if !reflect.DeepEqual(result, want) {
t.Errorf("diff: %s", test.Diff(want, result))
t.Errorf("diff %s: %s", tc, test.Diff(want, result))
}
}
})

View File

@@ -12,20 +12,24 @@ import (
const LabelPrefix = "docker_label_"
// AddLabels appends Docker labels to the Node from a topology.
func AddLabels(nmd report.Node, labels map[string]string) {
func AddLabels(node report.Node, labels map[string]string) report.Node {
node = node.Copy()
for key, value := range labels {
nmd.Metadata[LabelPrefix+key] = value
node = node.WithLatests(map[string]string{
LabelPrefix + key: value,
})
}
return node
}
// ExtractLabels returns the list of Docker labels given a Node from a topology.
func ExtractLabels(nmd report.Node) map[string]string {
func ExtractLabels(node report.Node) map[string]string {
result := map[string]string{}
for key, value := range nmd.Metadata {
node.Latest.ForEach(func(key, value string) {
if strings.HasPrefix(key, LabelPrefix) {
label := key[len(LabelPrefix):]
result[label] = value
}
}
})
return result
}

View File

@@ -16,7 +16,7 @@ func TestLabels(t *testing.T) {
}
nmd := report.MakeNode()
docker.AddLabels(nmd, want)
nmd = docker.AddLabels(nmd, want)
have := docker.ExtractLabels(nmd)
if !reflect.DeepEqual(want, have) {

View File

@@ -100,6 +100,7 @@ func NewRegistry(interval time.Duration, pipes controls.PipeClient) (Registry, e
// Stop stops the Docker registry's event subscriber.
func (r *registry) Stop() {
r.deregisterControls()
ch := make(chan struct{})
r.quit <- ch
<-ch

View File

@@ -53,7 +53,9 @@ func (c *mockContainer) GetNode(_ string, _ []net.IP) report.Node {
docker.ContainerID: c.c.ID,
docker.ContainerName: c.c.Name,
docker.ImageID: c.c.Image,
})
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.c.Image))),
)
}
func (c *mockContainer) Container() *client.Container {

View File

@@ -115,17 +115,17 @@ func (r *Reporter) containerImageTopology() report.Topology {
result := report.MakeTopology()
r.registry.WalkImages(func(image *docker_client.APIImages) {
nmd := report.MakeNodeWith(map[string]string{
node := report.MakeNodeWith(map[string]string{
ImageID: image.ID,
})
AddLabels(nmd, image.Labels)
node = AddLabels(node, image.Labels)
if len(image.RepoTags) > 0 {
nmd.Metadata[ImageName] = image.RepoTags[0]
node = node.WithLatests(map[string]string{ImageName: image.RepoTags[0]})
}
nodeID := report.MakeContainerImageNodeID(image.ID)
result.AddNode(nodeID, nmd)
result.AddNode(nodeID, node)
})
return result

View File

@@ -1,14 +1,12 @@
package docker_test
import (
"reflect"
"testing"
client "github.com/fsouza/go-dockerclient"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
type mockRegistry struct {
@@ -52,66 +50,60 @@ var (
)
func TestReporter(t *testing.T) {
want := report.MakeReport()
want.Container = report.Topology{
Nodes: report.Nodes{
report.MakeContainerNodeID("ping"): report.MakeNodeWith(map[string]string{
docker.ContainerID: "ping",
docker.ContainerName: "pong",
docker.ImageID: "baz",
}),
},
Controls: report.Controls{
docker.RestartContainer: report.Control{
ID: docker.RestartContainer,
Human: "Restart",
Icon: "fa-repeat",
},
docker.StartContainer: report.Control{
ID: docker.StartContainer,
Human: "Start",
Icon: "fa-play",
},
docker.StopContainer: report.Control{
ID: docker.StopContainer,
Human: "Stop",
Icon: "fa-stop",
},
docker.PauseContainer: report.Control{
ID: docker.PauseContainer,
Human: "Pause",
Icon: "fa-pause",
},
docker.UnpauseContainer: report.Control{
ID: docker.UnpauseContainer,
Human: "Unpause",
Icon: "fa-play",
},
docker.AttachContainer: report.Control{
ID: docker.AttachContainer,
Human: "Attach",
Icon: "fa-desktop",
},
docker.ExecContainer: report.Control{
ID: docker.ExecContainer,
Human: "Exec /bin/sh",
Icon: "fa-terminal",
},
},
}
want.ContainerImage = report.Topology{
Nodes: report.Nodes{
report.MakeContainerImageNodeID("baz"): report.MakeNodeWith(map[string]string{
docker.ImageID: "baz",
docker.ImageName: "bang",
}),
},
Controls: report.Controls{},
containerImageNodeID := report.MakeContainerImageNodeID("baz")
rpt, err := docker.NewReporter(mockRegistryInstance, "host1", nil).Report()
if err != nil {
t.Fatal(err)
}
reporter := docker.NewReporter(mockRegistryInstance, "host1", nil)
have, _ := reporter.Report()
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
// Reporter should add a container
{
containerNodeID := report.MakeContainerNodeID("ping")
node, ok := rpt.Container.Nodes[containerNodeID]
if !ok {
t.Fatalf("Expected report to have container image %q, but not found", containerNodeID)
}
for k, want := range map[string]string{
docker.ContainerID: "ping",
docker.ContainerName: "pong",
docker.ImageID: "baz",
} {
if have, ok := node.Latest.Lookup(k); !ok || have != want {
t.Errorf("Expected container %s latest %q: %q, got %q", containerNodeID, k, want, have)
}
}
// container should have controls
if len(rpt.Container.Controls) == 0 {
t.Errorf("Container should have some controls")
}
// container should have the image as a parent
if parents, ok := node.Parents.Lookup(report.ContainerImage); !ok || !parents.Contains(containerImageNodeID) {
t.Errorf("Expected container %s to have parent container image %q, got %q", containerNodeID, containerImageNodeID, parents)
}
}
// Reporter should add a container image
{
node, ok := rpt.ContainerImage.Nodes[containerImageNodeID]
if !ok {
t.Fatalf("Expected report to have container image %q, but not found", containerImageNodeID)
}
for k, want := range map[string]string{
docker.ImageID: "baz",
docker.ImageName: "bang",
} {
if have, ok := node.Latest.Lookup(k); !ok || have != want {
t.Errorf("Expected container image %s latest %q: %q, got %q", containerImageNodeID, k, want, have)
}
}
// container image should have no controls
if len(rpt.ContainerImage.Controls) != 0 {
t.Errorf("Container images should not have any controls")
}
}
}

View File

@@ -49,7 +49,7 @@ func (t *Tagger) Tag(r report.Report) (report.Report, error) {
func (t *Tagger) tag(tree process.Tree, topology *report.Topology) {
for nodeID, node := range topology.Nodes {
pidStr, ok := node.Metadata[process.PID]
pidStr, ok := node.Latest.Lookup(process.PID)
if !ok {
continue
}
@@ -84,9 +84,9 @@ func (t *Tagger) tag(tree process.Tree, topology *report.Topology) {
topology.AddNode(nodeID, report.MakeNodeWith(map[string]string{
ContainerID: c.ID(),
}).WithParents(report.Sets{
report.Container: report.MakeStringSet(report.MakeContainerNodeID(c.ID())),
report.ContainerImage: report.MakeStringSet(report.MakeContainerImageNodeID(c.Image())),
}))
}).WithParents(report.EmptySets.
Add(report.Container, report.MakeStringSet(report.MakeContainerNodeID(c.ID()))).
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.Image()))),
))
}
}

View File

@@ -2,13 +2,13 @@ package docker_test
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
type mockProcessTree struct {
@@ -28,6 +28,9 @@ func (m *mockProcessTree) GetChildren(int) ([]int, error) {
}
func TestTagger(t *testing.T) {
mtime.NowForce(time.Now())
defer mtime.NowReset()
oldProcessTree := docker.NewProcessTreeStub
defer func() { docker.NewProcessTreeStub = oldProcessTree }()
@@ -38,28 +41,37 @@ func TestTagger(t *testing.T) {
var (
pid1NodeID = report.MakeProcessNodeID("somehost.com", "2")
pid2NodeID = report.MakeProcessNodeID("somehost.com", "3")
wantNode = report.MakeNodeWith(map[string]string{
docker.ContainerID: "ping",
}).WithParents(report.Sets{
report.Container: report.MakeStringSet(report.MakeContainerNodeID("ping")),
report.ContainerImage: report.MakeStringSet(report.MakeContainerImageNodeID("baz")),
})
)
input := report.MakeReport()
input.Process.AddNode(pid1NodeID, report.MakeNodeWith(map[string]string{process.PID: "2"}))
input.Process.AddNode(pid2NodeID, report.MakeNodeWith(map[string]string{process.PID: "3"}))
want := report.MakeReport()
want.Process.AddNode(pid1NodeID, report.MakeNodeWith(map[string]string{process.PID: "2"}).Merge(wantNode))
want.Process.AddNode(pid2NodeID, report.MakeNodeWith(map[string]string{process.PID: "3"}).Merge(wantNode))
tagger := docker.NewTagger(mockRegistryInstance, nil)
have, err := tagger.Tag(input)
have, err := docker.NewTagger(mockRegistryInstance, nil).Tag(input)
if err != nil {
t.Errorf("%v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
// Processes should be tagged with their container ID, and parents
for _, nodeID := range []string{pid1NodeID, pid2NodeID} {
node, ok := have.Process.Nodes[nodeID]
if !ok {
t.Errorf("Expected process node %s, but not found", nodeID)
}
// node should have the container id added
if have, ok := node.Latest.Lookup(docker.ContainerID); !ok || have != "ping" {
t.Errorf("Expected process node %s to have container id %q, got %q", nodeID, "ping", have)
}
// node should have the container as a parent
if have, ok := node.Parents.Lookup(report.Container); !ok || !have.Contains(report.MakeContainerNodeID("ping")) {
t.Errorf("Expected process node %s to have container %q as a parent, got %q", nodeID, "ping", have)
}
// node should have the container image as a parent
if have, ok := node.Parents.Lookup(report.ContainerImage); !ok || !have.Contains(report.MakeContainerImageNodeID("baz")) {
t.Errorf("Expected process node %s to have container image %q as a parent, got %q", nodeID, "baz", have)
}
}
}

View File

@@ -61,10 +61,10 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) {
return
}
node = node.Copy()
node.Metadata[Addr] = mapping.rewrittenIP
node.Metadata[Port] = copyEndpointPort
node.Metadata["copy_of"] = realEndpointID
rpt.Endpoint.AddNode(copyEndpointID, node)
rpt.Endpoint.AddNode(copyEndpointID, node.WithLatests(map[string]string{
Addr: mapping.rewrittenIP,
Port: copyEndpointPort,
"copy_of": realEndpointID,
}))
})
}

View File

@@ -1,11 +1,12 @@
package endpoint
import (
"reflect"
"testing"
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
type mockFlowWalker struct {
@@ -21,6 +22,9 @@ func (m *mockFlowWalker) walkFlows(f func(flow)) {
func (m *mockFlowWalker) stop() {}
func TestNat(t *testing.T) {
mtime.NowForce(mtime.Now())
defer mtime.NowReset()
// test that two containers, on the docker network, get their connections mapped
// correctly.
// the setup is this:
@@ -40,14 +44,14 @@ func TestNat(t *testing.T) {
have := report.MakeReport()
originalID := report.MakeEndpointNodeID("host1", "10.0.47.1", "80")
have.Endpoint.AddNode(originalID, report.MakeNodeWith(report.Metadata{
have.Endpoint.AddNode(originalID, report.MakeNodeWith(map[string]string{
Addr: "10.0.47.1",
Port: "80",
"foo": "bar",
}))
want := have.Copy()
want.Endpoint.AddNode(report.MakeEndpointNodeID("host1", "1.2.3.4", "80"), report.MakeNodeWith(report.Metadata{
want.Endpoint.AddNode(report.MakeEndpointNodeID("host1", "1.2.3.4", "80"), report.MakeNodeWith(map[string]string{
Addr: "1.2.3.4",
Port: "80",
"copy_of": originalID,
@@ -72,14 +76,14 @@ func TestNat(t *testing.T) {
have := report.MakeReport()
originalID := report.MakeEndpointNodeID("host2", "10.0.47.2", "22222")
have.Endpoint.AddNode(originalID, report.MakeNodeWith(report.Metadata{
have.Endpoint.AddNode(originalID, report.MakeNodeWith(map[string]string{
Addr: "10.0.47.2",
Port: "22222",
"foo": "baz",
}))
want := have.Copy()
want.Endpoint.AddNode(report.MakeEndpointNodeID("host2", "2.3.4.5", "22223"), report.MakeNodeWith(report.Metadata{
want.Endpoint.AddNode(report.MakeEndpointNodeID("host2", "2.3.4.5", "22223"), report.MakeNodeWith(map[string]string{
Addr: "2.3.4.5",
Port: "22223",
"copy_of": originalID,

View File

@@ -84,7 +84,7 @@ func (r *Reporter) Report() (report.Report, error) {
if err != nil {
return rpt, err
}
commonNodeInfo := report.MakeNode().WithMetadata(report.Metadata{
commonNodeInfo := report.MakeNode().WithLatests(map[string]string{
Procspied: "true",
})
for conn := conns.Next(); conn != nil; conn = conns.Next() {
@@ -96,7 +96,7 @@ func (r *Reporter) Report() (report.Report, error) {
)
extraNodeInfo := commonNodeInfo.Copy()
if conn.Proc.PID > 0 {
extraNodeInfo = extraNodeInfo.WithMetadata(report.Metadata{
extraNodeInfo = extraNodeInfo.WithLatests(map[string]string{
process.PID: strconv.FormatUint(uint64(conn.Proc.PID), 10),
report.HostNodeID: hostNodeID,
})
@@ -107,7 +107,7 @@ func (r *Reporter) Report() (report.Report, error) {
// Consult the flowWalker for short-live connections
{
extraNodeInfo := report.MakeNode().WithMetadata(report.Metadata{
extraNodeInfo := report.MakeNode().WithLatests(map[string]string{
Conntracked: "true",
})
r.flowWalker.walkFlows(func(f flow) {

View File

@@ -86,7 +86,8 @@ func TestSpyNoProcesses(t *testing.T) {
scopedRemote = report.MakeAddressNodeID(nodeID, fixRemoteAddress.String())
)
if want, have := nodeName, r.Address.Nodes[scopedLocal].Metadata[docker.Name]; want != have {
have, _ := r.Address.Nodes[scopedLocal].Latest.Lookup(docker.Name)
if want, have := nodeName, have; want != have {
t.Fatalf("want %q, have %q", want, have)
}
@@ -127,7 +128,8 @@ func TestSpyWithProcesses(t *testing.T) {
for key, want := range map[string]string{
"pid": strconv.FormatUint(uint64(fixProcessPID), 10),
} {
if have := r.Endpoint.Nodes[scopedLocal].Metadata[key]; want != have {
have, _ := r.Endpoint.Nodes[scopedLocal].Latest.Lookup(key)
if want != have {
t.Errorf("Process.Nodes[%q][%q]: want %q, have %q", scopedLocal, key, want, have)
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/weaveworks/scope/report"
)
// Keys for use in Node.Metadata.
// Keys for use in Node.Latest.
const (
Timestamp = "ts"
HostName = "host_name"
@@ -85,9 +85,9 @@ func (r *Reporter) Report() (report.Report, error) {
OS: runtime.GOOS,
KernelVersion: kernel,
Uptime: uptime.String(),
}).WithSets(report.Sets{
LocalNetworks: report.MakeStringSet(localCIDRs...),
}).WithMetrics(metrics))
}).WithSets(report.EmptySets.
Add(LocalNetworks, report.MakeStringSet(localCIDRs...)),
).WithMetrics(metrics))
return rep, nil
}

View File

@@ -2,7 +2,6 @@ package host_test
import (
"net"
"reflect"
"runtime"
"testing"
"time"
@@ -10,7 +9,6 @@ import (
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
func TestReporter(t *testing.T) {
@@ -21,7 +19,7 @@ func TestReporter(t *testing.T) {
hostID = "hostid"
hostname = "hostname"
timestamp = time.Now()
load = report.Metrics{
metrics = report.Metrics{
host.Load1: report.MakeMetric().Add(timestamp, 1.0),
host.Load5: report.MakeMetric().Add(timestamp, 5.0),
host.Load15: report.MakeMetric().Add(timestamp, 15.0),
@@ -52,23 +50,51 @@ func TestReporter(t *testing.T) {
host.GetMemoryUsageBytes = oldGetMemoryUsageBytes
}()
host.GetKernelVersion = func() (string, error) { return release + " " + version, nil }
host.GetLoad = func(time.Time) report.Metrics { return load }
host.GetLoad = func(time.Time) report.Metrics { return metrics }
host.GetUptime = func() (time.Duration, error) { return time.ParseDuration(uptime) }
host.GetCPUUsagePercent = func() (float64, float64) { return 30.0, 100.0 }
host.GetMemoryUsageBytes = func() (float64, float64) { return 60.0, 100.0 }
want := report.MakeReport()
want.Host.AddNode(report.MakeHostNodeID(hostID), report.MakeNodeWith(map[string]string{
host.Timestamp: timestamp.UTC().Format(time.RFC3339Nano),
host.HostName: hostname,
host.OS: runtime.GOOS,
host.Uptime: uptime,
host.KernelVersion: kernel,
}).WithSets(report.Sets{
host.LocalNetworks: report.MakeStringSet(network),
}).WithMetrics(load))
have, _ := host.NewReporter(hostID, hostname, localNets).Report()
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
rpt, err := host.NewReporter(hostID, hostname, localNets).Report()
if err != nil {
t.Fatal(err)
}
nodeID := report.MakeHostNodeID(hostID)
node, ok := rpt.Host.Nodes[nodeID]
if !ok {
t.Errorf("Expected host node %q, but not found", nodeID)
}
// Should have a bunch of expected latest keys
for _, tuple := range []struct {
key, want string
}{
{host.Timestamp, timestamp.UTC().Format(time.RFC3339Nano)},
{host.HostName, hostname},
{host.OS, runtime.GOOS},
{host.Uptime, uptime},
{host.KernelVersion, kernel},
} {
if have, ok := node.Latest.Lookup(tuple.key); !ok || have != tuple.want {
t.Errorf("Expected %s %q, got %q", tuple.key, tuple.want, have)
}
}
// Should have the local network
if have, ok := node.Sets.Lookup(host.LocalNetworks); !ok || !have.Contains(network) {
t.Errorf("Expected host.LocalNetworks to include %q, got %q", network, have)
}
// Should have metrics
for key, want := range metrics {
wantSample := want.LastSample()
if metric, ok := node.Metrics[key]; !ok {
t.Errorf("Expected %s metric, but not found", key)
} else if sample := metric.LastSample(); sample == nil {
t.Errorf("Expected %s metric to have a sample, but there were none", key)
} else if sample.Value != wantSample.Value {
t.Errorf("Expected %s metric sample %f, got %f", key, wantSample, sample.Value)
}
}
}

View File

@@ -31,16 +31,14 @@ func (t Tagger) Tag(r report.Report) (report.Report, error) {
report.HostNodeID: t.hostNodeID,
report.ProbeID: t.probeID,
}
parents = report.Sets{
report.Host: report.MakeStringSet(t.hostNodeID),
}
parents = report.EmptySets.Add(report.Host, report.MakeStringSet(t.hostNodeID))
)
// Explicity don't tag Endpoints and Addresses - These topologies include pseudo nodes,
// and as such do their own host tagging
for _, topology := range []report.Topology{r.Process, r.Container, r.ContainerImage, r.Host, r.Overlay} {
for id, node := range topology.Nodes {
topology.AddNode(id, node.WithMetadata(metadata).WithParents(parents))
topology.AddNode(id, node.WithLatests(metadata).WithParents(parents))
}
}
return r, nil

View File

@@ -1,12 +1,10 @@
package host_test
import (
"reflect"
"testing"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
func TestTagger(t *testing.T) {
@@ -14,20 +12,34 @@ func TestTagger(t *testing.T) {
hostID = "foo"
probeID = "a1b2c3d4"
endpointNodeID = report.MakeEndpointNodeID(hostID, "1.2.3.4", "56789") // hostID ignored
nodeMetadata = report.MakeNodeWith(map[string]string{"foo": "bar"})
node = report.MakeNodeWith(map[string]string{"foo": "bar"})
)
r := report.MakeReport()
r.Process.AddNode(endpointNodeID, nodeMetadata)
want := nodeMetadata.Merge(report.MakeNodeWith(map[string]string{
report.HostNodeID: report.MakeHostNodeID(hostID),
report.ProbeID: probeID,
}).WithParents(report.Sets{
report.Host: report.MakeStringSet(report.MakeHostNodeID(hostID)),
}))
r.Process.AddNode(endpointNodeID, node)
rpt, _ := host.NewTagger(hostID, probeID).Tag(r)
have := rpt.Process.Nodes[endpointNodeID].Copy()
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
// It should now have the host ID
wantHostID := report.MakeHostNodeID(hostID)
if hostID, ok := have.Latest.Lookup(report.HostNodeID); !ok || hostID != wantHostID {
t.Errorf("Expected %q got %q", wantHostID, report.MakeHostNodeID(hostID))
}
// It should now have the probe ID
if haveProbeID, ok := have.Latest.Lookup(report.ProbeID); !ok || haveProbeID != probeID {
t.Errorf("Expected %q got %q", probeID, haveProbeID)
}
// It should still have the other keys
want := "bar"
if have, ok := have.Latest.Lookup("foo"); !ok || have != want {
t.Errorf("Expected %q got %q", want, have)
}
// It should have the host as a parent
wantParent := report.MakeHostNodeID(hostID)
if have, ok := have.Parents.Lookup(report.Host); !ok || len(have) != 1 || have[0] != wantParent {
t.Errorf("Expected %q got %q", report.MakeStringSet(wantParent), have)
}
}

View File

@@ -82,16 +82,16 @@ func (p *pod) GetNode() report.Node {
PodContainerIDs: strings.Join(p.ContainerIDs(), " "),
})
if len(p.serviceIDs) > 0 {
n.Metadata[ServiceIDs] = strings.Join(p.serviceIDs, " ")
n = n.WithLatests(map[string]string{ServiceIDs: strings.Join(p.serviceIDs, " ")})
}
for _, serviceID := range p.serviceIDs {
segments := strings.SplitN(serviceID, "/", 2)
if len(segments) != 2 {
continue
}
n = n.WithParents(report.Sets{
report.Service: report.MakeStringSet(report.MakeServiceNodeID(p.Namespace(), segments[1])),
})
n = n.WithParents(report.EmptySets.
Add(report.Service, report.MakeStringSet(report.MakeServiceNodeID(p.Namespace(), segments[1]))),
)
}
return n
}

View File

@@ -64,9 +64,7 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topo
container := report.MakeNodeWith(map[string]string{
PodID: p.ID(),
Namespace: p.Namespace(),
}).WithParents(report.Sets{
report.Pod: report.MakeStringSet(nodeID),
})
}).WithParents(report.EmptySets.Add(report.Pod, report.MakeStringSet(nodeID)))
for _, containerID := range p.ContainerIDs() {
containers.AddNode(report.MakeContainerNodeID(containerID), container)
}

View File

@@ -1,7 +1,6 @@
package kubernetes_test
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
@@ -9,7 +8,6 @@ import (
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
var (
@@ -108,60 +106,94 @@ func (c *mockClient) WalkServices(f func(kubernetes.Service) error) error {
}
func TestReporter(t *testing.T) {
want := report.MakeReport()
pod1ID := report.MakePodNodeID("ping", "pong-a")
pod2ID := report.MakePodNodeID("ping", "pong-b")
serviceID := report.MakeServiceNodeID("ping", "pongservice")
want.Pod = report.MakeTopology().AddNode(pod1ID, report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-a",
kubernetes.PodName: "pong-a",
kubernetes.Namespace: "ping",
kubernetes.PodCreated: pod1.Created(),
kubernetes.PodContainerIDs: "container1 container2",
kubernetes.ServiceIDs: "ping/pongservice",
}).WithParents(report.Sets{
report.Service: report.MakeStringSet(serviceID),
})).AddNode(pod2ID, report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-b",
kubernetes.PodName: "pong-b",
kubernetes.Namespace: "ping",
kubernetes.PodCreated: pod1.Created(),
kubernetes.PodContainerIDs: "container3 container4",
kubernetes.ServiceIDs: "ping/pongservice",
}).WithParents(report.Sets{
report.Service: report.MakeStringSet(serviceID),
}))
want.Service = report.MakeTopology().AddNode(serviceID, report.MakeNodeWith(map[string]string{
kubernetes.ServiceID: "ping/pongservice",
kubernetes.ServiceName: "pongservice",
kubernetes.Namespace: "ping",
kubernetes.ServiceCreated: pod1.Created(),
}))
want.Container = report.MakeTopology().AddNode(report.MakeContainerNodeID("container1"), report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-a",
kubernetes.Namespace: "ping",
}).WithParents(report.Sets{
report.Pod: report.MakeStringSet(pod1ID),
})).AddNode(report.MakeContainerNodeID("container2"), report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-a",
kubernetes.Namespace: "ping",
}).WithParents(report.Sets{
report.Pod: report.MakeStringSet(pod1ID),
})).AddNode(report.MakeContainerNodeID("container3"), report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-b",
kubernetes.Namespace: "ping",
}).WithParents(report.Sets{
report.Pod: report.MakeStringSet(pod2ID),
})).AddNode(report.MakeContainerNodeID("container4"), report.MakeNodeWith(map[string]string{
kubernetes.PodID: "ping/pong-b",
kubernetes.Namespace: "ping",
}).WithParents(report.Sets{
report.Pod: report.MakeStringSet(pod2ID),
}))
rpt, _ := kubernetes.NewReporter(mockClientInstance).Report()
reporter := kubernetes.NewReporter(mockClientInstance)
have, _ := reporter.Report()
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
// Reporter should have added the following pods
for _, pod := range []struct {
id string
parentService string
latest map[string]string
}{
{pod1ID, serviceID, map[string]string{
kubernetes.PodID: "ping/pong-a",
kubernetes.PodName: "pong-a",
kubernetes.Namespace: "ping",
kubernetes.PodCreated: pod1.Created(),
kubernetes.PodContainerIDs: "container1 container2",
kubernetes.ServiceIDs: "ping/pongservice",
}},
{pod2ID, serviceID, map[string]string{
kubernetes.PodID: "ping/pong-b",
kubernetes.PodName: "pong-b",
kubernetes.Namespace: "ping",
kubernetes.PodCreated: pod1.Created(),
kubernetes.PodContainerIDs: "container3 container4",
kubernetes.ServiceIDs: "ping/pongservice",
}},
} {
node, ok := rpt.Pod.Nodes[pod.id]
if !ok {
t.Errorf("Expected report to have pod %q, but not found", pod.id)
}
if parents, ok := node.Parents.Lookup(report.Service); !ok || !parents.Contains(pod.parentService) {
t.Errorf("Expected pod %s to have parent service %q, got %q", pod.id, pod.parentService, parents)
}
for k, want := range pod.latest {
if have, ok := node.Latest.Lookup(k); !ok || have != want {
t.Errorf("Expected pod %s latest %q: %q, got %q", pod.id, k, want, have)
}
}
}
// Reporter should have added a service
{
node, ok := rpt.Service.Nodes[serviceID]
if !ok {
t.Errorf("Expected report to have service %q, but not found", serviceID)
}
for k, want := range map[string]string{
kubernetes.ServiceID: "ping/pongservice",
kubernetes.ServiceName: "pongservice",
kubernetes.Namespace: "ping",
kubernetes.ServiceCreated: pod1.Created(),
} {
if have, ok := node.Latest.Lookup(k); !ok || have != want {
t.Errorf("Expected service %s latest %q: %q, got %q", serviceID, k, want, have)
}
}
}
// Reporter should have tagged the containers
for _, pod := range []struct {
id, nodeID string
containers []string
}{
{"ping/pong-a", pod1ID, []string{"container1", "container2"}},
{"ping/pong-b", pod2ID, []string{"container3", "container4"}},
} {
for _, containerID := range pod.containers {
node, ok := rpt.Container.Nodes[report.MakeContainerNodeID(containerID)]
if !ok {
t.Errorf("Expected report to have container %q, but not found", containerID)
}
// container should have pod id
if have, ok := node.Latest.Lookup(kubernetes.PodID); !ok || have != pod.id {
t.Errorf("Expected container %s latest %q: %q, got %q", containerID, kubernetes.PodID, pod.id, have)
}
// container should have namespace
if have, ok := node.Latest.Lookup(kubernetes.Namespace); !ok || have != "ping" {
t.Errorf("Expected container %s latest %q: %q, got %q", containerID, kubernetes.Namespace, "ping", have)
}
// container should have pod parent
if parents, ok := node.Parents.Lookup(report.Pod); !ok || !parents.Contains(pod.nodeID) {
t.Errorf("Expected container %s to have parent service %q, got %q", containerID, pod.nodeID, parents)
}
}
}
}

View File

@@ -200,16 +200,18 @@ func (w *Weave) Tag(r report.Report) (report.Report, error) {
if !ok {
continue
}
hostnames := report.IDList(strings.Fields(node.Metadata[WeaveDNSHostname]))
w, _ := node.Latest.Lookup(WeaveDNSHostname)
hostnames := report.IDList(strings.Fields(w))
hostnames = hostnames.Add(strings.TrimSuffix(entry.Hostname, "."))
node.Metadata[WeaveDNSHostname] = strings.Join(hostnames, " ")
r.Container.Nodes[nodeID] = node.WithLatests(map[string]string{WeaveDNSHostname: strings.Join(hostnames, " ")})
}
// Put information from weave ps on the container nodes
w.mtx.RLock()
defer w.mtx.RUnlock()
for id, node := range r.Container.Nodes {
prefix := node.Metadata[docker.ContainerID][:12]
prefix, _ := node.Latest.Lookup(docker.ContainerID)
prefix = prefix[:12]
entry, ok := w.ps[prefix]
if !ok {
continue
@@ -221,7 +223,7 @@ func (w *Weave) Tag(r report.Report) (report.Report, error) {
}
node = node.WithSet(docker.ContainerIPs, report.MakeStringSet(entry.ips...))
node = node.WithSet(docker.ContainerIPsWithScopes, ipsWithScope)
node.Metadata[WeaveMACAddress] = entry.macAddress
node = node.WithLatests(map[string]string{WeaveMACAddress: entry.macAddress})
r.Container.Nodes[id] = node
}
return r, nil

View File

@@ -4,21 +4,21 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/weaveworks/scope/common/exec"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/overlay"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
testExec "github.com/weaveworks/scope/test/exec"
)
func TestWeaveTaggerOverlayTopology(t *testing.T) {
wait := make(chan struct{})
oldExecCmd := exec.Command
defer func() { exec.Command = oldExecCmd }()
exec.Command = func(name string, args ...string) exec.Cmd {
close(wait)
return testExec.NewMockCmdString(fmt.Sprintf("%s %s %s/24\n", mockContainerID, mockContainerMAC, mockContainerIP))
}
@@ -28,35 +28,31 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) {
w := overlay.NewWeave(mockHostID, s.URL)
defer w.Stop()
w.Tick()
<-wait
{
// Overlay node should include peer name and nickname
have, err := w.Report()
if err != nil {
t.Fatal(err)
}
if want, have := report.MakeTopology().AddNode(
report.MakeOverlayNodeID(mockWeavePeerName),
report.MakeNodeWith(map[string]string{
overlay.WeavePeerName: mockWeavePeerName,
overlay.WeavePeerNickName: mockWeavePeerNickName,
}),
), have.Overlay; !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
nodeID := report.MakeOverlayNodeID(mockWeavePeerName)
node, ok := have.Overlay.Nodes[nodeID]
if !ok {
t.Errorf("Expected overlay node %q, but not found", nodeID)
}
if peerName, ok := node.Latest.Lookup(overlay.WeavePeerName); !ok || peerName != mockWeavePeerName {
t.Errorf("Expected weave peer name %q, got %q", mockWeavePeerName, peerName)
}
if peerNick, ok := node.Latest.Lookup(overlay.WeavePeerNickName); !ok || peerNick != mockWeavePeerNickName {
t.Errorf("Expected weave peer nickname %q, got %q", mockWeavePeerNickName, peerNick)
}
}
{
// Container nodes should be tagged with their overlay info
nodeID := report.MakeContainerNodeID(mockContainerID)
want := report.Report{
Container: report.MakeTopology().AddNode(nodeID, report.MakeNodeWith(map[string]string{
docker.ContainerID: mockContainerID,
overlay.WeaveDNSHostname: mockHostname,
overlay.WeaveMACAddress: mockContainerMAC,
}).WithSets(report.Sets{
docker.ContainerIPs: report.MakeStringSet(mockContainerIP),
docker.ContainerIPsWithScopes: report.MakeStringSet(mockContainerIPWithScope),
})),
}
have, err := w.Tag(report.Report{
Container: report.MakeTopology().AddNode(nodeID, report.MakeNodeWith(map[string]string{
docker.ContainerID: mockContainerID,
@@ -65,8 +61,27 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
node, ok := have.Container.Nodes[nodeID]
if !ok {
t.Errorf("Expected container node %q, but not found", nodeID)
}
// Should have Weave DNS Hostname
if have, ok := node.Latest.Lookup(overlay.WeaveDNSHostname); !ok || have != mockHostname {
t.Errorf("Expected weave dns hostname %q, got %q", mockHostname, have)
}
// Should have Weave MAC Address
if have, ok := node.Latest.Lookup(overlay.WeaveMACAddress); !ok || have != mockContainerMAC {
t.Errorf("Expected weave mac address %q, got %q", mockContainerMAC, have)
}
// Should have Weave container ip
if have, ok := node.Sets.Lookup(docker.ContainerIPs); !ok || !have.Contains(mockContainerIP) {
t.Errorf("Expected container ips to include the weave IP %q, got %q", mockContainerIP, have)
}
// Should have Weave container ip (with scope)
if have, ok := node.Sets.Lookup(docker.ContainerIPsWithScopes); !ok || !have.Contains(mockContainerIPWithScope) {
t.Errorf("Expected container ips to include the weave IP (with scope) %q, got %q", mockContainerIPWithScope, have)
}
}
}

View File

@@ -70,12 +70,12 @@ func (r *Reporter) processTopology() (report.Topology, error) {
{Threads, strconv.Itoa(p.Threads)},
} {
if tuple.value != "" {
node.Metadata[tuple.key] = tuple.value
node = node.WithLatests(map[string]string{tuple.key: tuple.value})
}
}
if p.PPID > 0 {
node.Metadata[PPID] = strconv.Itoa(p.PPID)
node = node.WithLatests(map[string]string{PPID: strconv.Itoa(p.PPID)})
}
if deltaTotal > 0 {

View File

@@ -1,14 +1,13 @@
package process_test
import (
"reflect"
"fmt"
"testing"
"time"
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
type mockWalker struct {
@@ -23,61 +22,79 @@ func (m *mockWalker) Walk(f func(process.Process, process.Process)) error {
}
func TestReporter(t *testing.T) {
walker := &mockWalker{
processes: []process.Process{
{PID: 1, PPID: 0, Name: "init"},
{PID: 2, PPID: 1, Name: "bash"},
{PID: 3, PPID: 1, Name: "apache", Threads: 2},
{PID: 4, PPID: 2, Name: "ping", Cmdline: "ping foo.bar.local"},
{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
},
processes := []process.Process{
{PID: 1, PPID: 0, Name: "init"},
{PID: 2, PPID: 1, Name: "bash"},
{PID: 3, PPID: 1, Name: "apache", Threads: 2},
{PID: 4, PPID: 2, Name: "ping", Cmdline: "ping foo.bar.local"},
{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
}
walker := &mockWalker{processes: processes}
getDeltaTotalJiffies := func() (uint64, float64, error) { return 0, 0., nil }
now := time.Now()
mtime.NowForce(now)
defer mtime.NowReset()
reporter := process.NewReporter(walker, "", getDeltaTotalJiffies)
want := report.MakeReport()
want.Process = report.MakeTopology().AddNode(
report.MakeProcessNodeID("", "1"), report.MakeNodeWith(map[string]string{
process.PID: "1",
process.Name: "init",
process.Threads: "0",
}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
).AddNode(
report.MakeProcessNodeID("", "2"), report.MakeNodeWith(map[string]string{
process.PID: "2",
process.Name: "bash",
process.PPID: "1",
process.Threads: "0",
}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
).AddNode(
report.MakeProcessNodeID("", "3"), report.MakeNodeWith(map[string]string{
process.PID: "3",
process.Name: "apache",
process.PPID: "1",
process.Threads: "2",
}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
).AddNode(
report.MakeProcessNodeID("", "4"), report.MakeNodeWith(map[string]string{
process.PID: "4",
process.Name: "ping",
process.PPID: "2",
process.Cmdline: "ping foo.bar.local",
process.Threads: "0",
}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
).AddNode(
report.MakeProcessNodeID("", "5"), report.MakeNodeWith(map[string]string{
process.PID: "5",
process.PPID: "1",
process.Cmdline: "tail -f /var/log/syslog",
process.Threads: "0",
}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
)
rpt, err := process.NewReporter(walker, "", getDeltaTotalJiffies).Report()
if err != nil {
t.Error(err)
}
have, err := reporter.Report()
if err != nil || !reflect.DeepEqual(want, have) {
t.Errorf("%s (%v)", test.Diff(want, have), err)
// It reports the init process
node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "1")]
if !ok {
t.Errorf("Expected report to include the pid 1 init")
}
if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[0].Name {
t.Errorf("Expected %q got %q", processes[0].Name, name)
}
// It reports plain processes (with parent pid, and metrics)
node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "2")]
if !ok {
t.Errorf("Expected report to include the pid 2 bash")
}
if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[1].Name {
t.Errorf("Expected %q got %q", processes[1].Name, name)
}
if ppid, ok := node.Latest.Lookup(process.PPID); !ok || ppid != fmt.Sprint(processes[1].PPID) {
t.Errorf("Expected %d got %q", processes[1].PPID, ppid)
}
if memoryUsage, ok := node.Metrics[process.MemoryUsage]; !ok {
t.Errorf("Expected memory usage metric, but not found")
} else if sample := memoryUsage.LastSample(); sample == nil {
t.Errorf("Expected memory usage metric to have a sample, but there were none")
} else if sample.Value != 0. {
t.Errorf("Expected memory usage metric sample %f, got %f", 0., sample.Value)
}
// It reports thread-counts
node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "3")]
if !ok {
t.Errorf("Expected report to include the pid 3 apache")
}
if threads, ok := node.Latest.Lookup(process.Threads); !ok || threads != fmt.Sprint(processes[2].Threads) {
t.Errorf("Expected %d got %q", processes[2].Threads, threads)
}
// It reports the Cmdline
node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "4")]
if !ok {
t.Errorf("Expected report to include the pid 4 ping")
}
if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[3].Cmdline) {
t.Errorf("Expected %q got %q", processes[3].Cmdline, cmdline)
}
// It reports processes without a Name
node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "5")]
if !ok {
t.Errorf("Expected report to include the pid 5 tail")
}
if name, ok := node.Latest.Lookup(process.Name); ok {
t.Errorf("Expected no name, but got %q", name)
}
if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[4].Cmdline) {
t.Errorf("Expected %q got %q", processes[4].Cmdline, cmdline)
}
}

View File

@@ -15,37 +15,37 @@ import (
var (
processNodeMetadata = renderMetadata(
meta(process.PID, "PID"),
meta(process.PPID, "Parent PID"),
meta(process.Cmdline, "Command"),
meta(process.Threads, "# Threads"),
ltst(process.PID, "PID"),
ltst(process.PPID, "Parent PID"),
ltst(process.Cmdline, "Command"),
ltst(process.Threads, "# Threads"),
)
containerNodeMetadata = renderMetadata(
meta(docker.ContainerID, "ID"),
meta(docker.ImageID, "Image ID"),
ltst(docker.ContainerID, "ID"),
ltst(docker.ImageID, "Image ID"),
ltst(docker.ContainerState, "State"),
sets(docker.ContainerIPs, "IPs"),
sets(docker.ContainerPorts, "Ports"),
meta(docker.ContainerCreated, "Created"),
meta(docker.ContainerCommand, "Command"),
meta(overlay.WeaveMACAddress, "Weave MAC"),
meta(overlay.WeaveDNSHostname, "Weave DNS Hostname"),
ltst(docker.ContainerCreated, "Created"),
ltst(docker.ContainerCommand, "Command"),
ltst(overlay.WeaveMACAddress, "Weave MAC"),
ltst(overlay.WeaveDNSHostname, "Weave DNS Hostname"),
getDockerLabelRows,
)
containerImageNodeMetadata = renderMetadata(
meta(docker.ImageID, "Image ID"),
ltst(docker.ImageID, "Image ID"),
getDockerLabelRows,
)
podNodeMetadata = renderMetadata(
meta(kubernetes.PodID, "ID"),
meta(kubernetes.Namespace, "Namespace"),
meta(kubernetes.PodCreated, "Created"),
ltst(kubernetes.PodID, "ID"),
ltst(kubernetes.Namespace, "Namespace"),
ltst(kubernetes.PodCreated, "Created"),
)
hostNodeMetadata = renderMetadata(
meta(host.HostName, "Hostname"),
meta(host.OS, "Operating system"),
meta(host.KernelVersion, "Kernel version"),
meta(host.Uptime, "Uptime"),
ltst(host.HostName, "Hostname"),
ltst(host.OS, "Operating system"),
ltst(host.KernelVersion, "Kernel version"),
ltst(host.Uptime, "Uptime"),
sets(host.LocalNetworks, "Local Networks"),
)
)
@@ -92,18 +92,9 @@ func renderMetadata(templates ...func(report.Node) []MetadataRow) func(report.No
}
}
func meta(id, label string) func(report.Node) []MetadataRow {
return func(n report.Node) []MetadataRow {
if val, ok := n.Metadata[id]; ok {
return []MetadataRow{{ID: id, Label: label, Value: val}}
}
return nil
}
}
func sets(id, label string) func(report.Node) []MetadataRow {
return func(n report.Node) []MetadataRow {
if val, ok := n.Sets[id]; ok && len(val) > 0 {
if val, ok := n.Sets.Lookup(id); ok && len(val) > 0 {
return []MetadataRow{{ID: id, Label: label, Value: strings.Join(val, ", ")}}
}
return nil

View File

@@ -22,9 +22,10 @@ func TestNodeMetadata(t *testing.T) {
node: report.MakeNodeWith(map[string]string{
docker.ContainerID: fixture.ClientContainerID,
docker.LabelPrefix + "label1": "label1value",
}).WithTopology(report.Container).WithSets(report.Sets{
docker.ContainerIPs: report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24"),
}).WithLatest(docker.ContainerState, fixture.Now, docker.StateRunning),
docker.ContainerState: docker.StateRunning,
}).WithTopology(report.Container).WithSets(report.EmptySets.
Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")),
),
want: []detailed.MetadataRow{
{ID: docker.ContainerID, Label: "ID", Value: fixture.ClientContainerID},
{ID: docker.ContainerState, Label: "State", Value: "running"},

View File

@@ -62,8 +62,9 @@ func controlsFor(topology report.Topology, nodeID string) []ControlInstance {
for _, id := range node.Controls.Controls {
if control, ok := topology.Controls[id]; ok {
probeID, _ := node.Latest.Lookup(report.ProbeID)
result = append(result, ControlInstance{
ProbeID: node.Metadata[report.ProbeID],
ProbeID: probeID,
NodeID: nodeID,
Control: control,
})
@@ -142,7 +143,8 @@ func parents(r report.Report, n render.RenderableNode) (result []Parent) {
sort.Strings(topologyIDs)
for _, topologyID := range topologyIDs {
t := topologies[topologyID]
for _, id := range n.Node.Parents[topologyID] {
parents, _ := n.Node.Parents.Lookup(topologyID)
for _, id := range parents {
if topologyID == n.Node.Topology && id == n.ID {
continue
}
@@ -160,31 +162,36 @@ func parents(r report.Report, n render.RenderableNode) (result []Parent) {
func containerParent(n report.Node) Parent {
label, _ := render.GetRenderableContainerName(n)
containerID, _ := n.Latest.Lookup(docker.ContainerID)
return Parent{
ID: render.MakeContainerID(n.Metadata[docker.ContainerID]),
ID: render.MakeContainerID(containerID),
Label: label,
TopologyID: "containers",
}
}
func podParent(n report.Node) Parent {
podID, _ := n.Latest.Lookup(kubernetes.PodID)
podName, _ := n.Latest.Lookup(kubernetes.PodName)
return Parent{
ID: render.MakePodID(n.Metadata[kubernetes.PodID]),
Label: n.Metadata[kubernetes.PodName],
ID: render.MakePodID(podID),
Label: podName,
TopologyID: "pods",
}
}
func serviceParent(n report.Node) Parent {
serviceID, _ := n.Latest.Lookup(kubernetes.ServiceID)
serviceName, _ := n.Latest.Lookup(kubernetes.ServiceName)
return Parent{
ID: render.MakeServiceID(n.Metadata[kubernetes.ServiceID]),
Label: n.Metadata[kubernetes.ServiceName],
ID: render.MakeServiceID(serviceID),
Label: serviceName,
TopologyID: "pods-by-service",
}
}
func containerImageParent(n report.Node) Parent {
imageName := n.Metadata[docker.ImageName]
imageName, _ := n.Latest.Lookup(docker.ImageName)
return Parent{
ID: render.MakeContainerImageID(render.ImageNameWithoutVersion(imageName)),
Label: imageName,
@@ -193,9 +200,10 @@ func containerImageParent(n report.Node) Parent {
}
func hostParent(n report.Node) Parent {
hostName, _ := n.Latest.Lookup(host.HostName)
return Parent{
ID: render.MakeHostID(n.Metadata[host.HostName]),
Label: n.Metadata[host.HostName],
ID: render.MakeHostID(hostName),
Label: hostName,
TopologyID: "hosts",
}
}

View File

@@ -75,15 +75,15 @@ func (n NodeSummary) Copy() NodeSummary {
func processNodeSummary(nmd report.Node) NodeSummary {
var (
id string
label, nameFound = nmd.Metadata[process.Name]
label, nameFound = nmd.Latest.Lookup(process.Name)
)
if pid, ok := nmd.Metadata[process.PID]; ok {
if pid, ok := nmd.Latest.Lookup(process.PID); ok {
if !nameFound {
label = fmt.Sprintf("(%s)", pid)
}
id = render.MakeProcessID(report.ExtractHostID(nmd), pid)
}
_, isConnected := nmd.Metadata[render.IsConnected]
_, isConnected := nmd.Latest.Lookup(render.IsConnected)
return NodeSummary{
ID: id,
Label: label,
@@ -95,8 +95,9 @@ func processNodeSummary(nmd report.Node) NodeSummary {
func containerNodeSummary(nmd report.Node) NodeSummary {
label, _ := render.GetRenderableContainerName(nmd)
containerID, _ := nmd.Latest.Lookup(docker.ContainerID)
return NodeSummary{
ID: render.MakeContainerID(nmd.Metadata[docker.ContainerID]),
ID: render.MakeContainerID(containerID),
Label: label,
Linkable: true,
Metadata: containerNodeMetadata(nmd),
@@ -105,7 +106,7 @@ func containerNodeSummary(nmd report.Node) NodeSummary {
}
func containerImageNodeSummary(nmd report.Node) NodeSummary {
imageName := nmd.Metadata[docker.ImageName]
imageName, _ := nmd.Latest.Lookup(docker.ImageName)
return NodeSummary{
ID: render.MakeContainerImageID(render.ImageNameWithoutVersion(imageName)),
Label: imageName,
@@ -115,18 +116,21 @@ func containerImageNodeSummary(nmd report.Node) NodeSummary {
}
func podNodeSummary(nmd report.Node) NodeSummary {
podID, _ := nmd.Latest.Lookup(kubernetes.PodID)
podName, _ := nmd.Latest.Lookup(kubernetes.PodName)
return NodeSummary{
ID: render.MakePodID(nmd.Metadata[kubernetes.PodID]),
Label: nmd.Metadata[kubernetes.PodName],
ID: render.MakePodID(podID),
Label: podName,
Linkable: true,
Metadata: podNodeMetadata(nmd),
}
}
func hostNodeSummary(nmd report.Node) NodeSummary {
hostName, _ := nmd.Latest.Lookup(host.HostName)
return NodeSummary{
ID: render.MakeHostID(nmd.Metadata[host.HostName]),
Label: nmd.Metadata[host.HostName],
ID: render.MakeHostID(hostName),
Label: hostName,
Linkable: true,
Metadata: hostNodeMetadata(nmd),
Metrics: hostNodeMetrics(nmd),

View File

@@ -45,9 +45,9 @@ func ColorConnected(r Renderer) Renderer {
}
for id := range connected {
node := input[id]
node.Metadata[IsConnected] = "true"
input[id] = node
input[id] = input[id].WithNode(report.MakeNodeWith(map[string]string{
IsConnected: "true",
}))
}
return input
},
@@ -136,7 +136,7 @@ func FilterUnconnected(r Renderer) Renderer {
return Filter{
Renderer: ColorConnected(r),
FilterFunc: func(node RenderableNode) bool {
_, ok := node.Metadata[IsConnected]
_, ok := node.Latest.Lookup(IsConnected)
return ok
},
}
@@ -163,21 +163,25 @@ func FilterSystem(r Renderer) Renderer {
return Filter{
Renderer: r,
FilterFunc: func(node RenderableNode) bool {
containerName := node.Metadata[docker.ContainerName]
containerName, _ := node.Latest.Lookup(docker.ContainerName)
if _, ok := systemContainerNames[containerName]; ok {
return false
}
imagePrefix := strings.SplitN(node.Metadata[docker.ImageName], ":", 2)[0] // :(
imageName, _ := node.Latest.Lookup(docker.ImageName)
imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :(
if _, ok := systemImagePrefixes[imagePrefix]; ok {
return false
}
if node.Metadata[docker.LabelPrefix+"works.weave.role"] == "system" {
roleLabel, _ := node.Latest.Lookup(docker.LabelPrefix + "works.weave.role")
if roleLabel == "system" {
return false
}
if node.Metadata[kubernetes.Namespace] == "kube-system" {
namespace, _ := node.Latest.Lookup(kubernetes.Namespace)
if namespace == "kube-system" {
return false
}
if strings.HasPrefix(node.Metadata[docker.LabelPrefix+"io.kubernetes.pod.name"], "kube-system/") {
podName, _ := node.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name")
if strings.HasPrefix(podName, "kube-system/") {
return false
}
return true

View File

@@ -42,24 +42,24 @@ type MapFunc func(RenderableNode, report.Networks) RenderableNodes
// 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 {
addr, ok := m.Metadata[endpoint.Addr]
addr, ok := m.Latest.Lookup(endpoint.Addr)
if !ok {
return RenderableNodes{}
}
port, ok := m.Metadata[endpoint.Port]
port, ok := m.Latest.Lookup(endpoint.Port)
if !ok {
return RenderableNodes{}
}
// We only show nodes found through procspy in this view.
_, procspied := m.Metadata[endpoint.Procspied]
_, procspied := m.Latest.Lookup(endpoint.Procspied)
if !procspied {
return RenderableNodes{}
}
// Nodes without a hostid are treated as psuedo nodes
if _, ok = m.Metadata[report.HostNodeID]; !ok {
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) {
@@ -92,7 +92,7 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode
rank = major
)
pid, pidOK := m.Metadata[process.PID]
pid, pidOK := m.Latest.Lookup(process.PID)
if pidOK {
minor = fmt.Sprintf("%s (%s)", minor, pid)
}
@@ -104,16 +104,16 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode
// 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.Metadata[process.PID]
pid, ok := m.Latest.Lookup(process.PID)
if !ok {
return RenderableNodes{}
}
var (
id = MakeProcessID(report.ExtractHostID(m.Node), pid)
major = m.Metadata[process.Name]
minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m.Node), pid)
rank = m.Metadata[process.Name]
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)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
@@ -123,7 +123,7 @@ func MapProcessIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
// 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.Metadata[docker.ContainerID]
containerID, ok := m.Latest.Lookup(docker.ContainerID)
if !ok {
return RenderableNodes{}
}
@@ -132,7 +132,7 @@ func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
id = MakeContainerID(containerID)
major, _ = GetRenderableContainerName(m.Node)
minor = report.ExtractHostID(m.Node)
rank = m.Metadata[docker.ImageID]
rank, _ = m.Latest.Lookup(docker.ImageID)
)
node := NewRenderableNodeWith(id, major, minor, rank, m)
@@ -148,7 +148,7 @@ func GetRenderableContainerName(nmd report.Node) (string, bool) {
//
// However, the ecs-agent provides a label containing the original Container
// Definition name.
if labelValue, ok := nmd.Metadata[docker.LabelPrefix+AmazonECSContainerNameLabel]; ok {
if labelValue, ok := nmd.Latest.Lookup(docker.LabelPrefix + AmazonECSContainerNameLabel); ok {
return labelValue, true
}
@@ -156,11 +156,11 @@ func GetRenderableContainerName(nmd report.Node) (string, bool) {
// 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.Metadata[docker.LabelPrefix+KubernetesContainerNameLabel]; ok {
if labelValue, ok := nmd.Latest.Lookup(docker.LabelPrefix + KubernetesContainerNameLabel); ok {
return labelValue, true
}
name, ok := nmd.Metadata[docker.ContainerName]
name, ok := nmd.Latest.Lookup(docker.ContainerName)
return name, ok
}
@@ -168,15 +168,15 @@ func GetRenderableContainerName(nmd report.Node) (string, bool) {
// 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.Metadata[docker.ImageID]
imageID, ok := m.Latest.Lookup(docker.ImageID)
if !ok {
return RenderableNodes{}
}
var (
id = MakeContainerImageID(imageID)
major = m.Metadata[docker.ImageName]
rank = imageID
id = MakeContainerImageID(imageID)
major, _ = m.Latest.Lookup(docker.ImageName)
rank = imageID
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
@@ -186,15 +186,15 @@ func MapContainerImageIdentity(m RenderableNode, _ report.Networks) RenderableNo
// only ever run on pod topology nodes, we expect that certain keys
// are present.
func MapPodIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
podID, ok := m.Metadata[kubernetes.PodID]
podID, ok := m.Latest.Lookup(kubernetes.PodID)
if !ok {
return RenderableNodes{}
}
var (
id = MakePodID(podID)
major = m.Metadata[kubernetes.PodName]
rank = m.Metadata[kubernetes.PodID]
id = MakePodID(podID)
major, _ = m.Latest.Lookup(kubernetes.PodName)
rank, _ = m.Latest.Lookup(kubernetes.PodID)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
@@ -204,15 +204,15 @@ func MapPodIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
// only ever run on service topology nodes, we expect that certain keys
// are present.
func MapServiceIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
serviceID, ok := m.Metadata[kubernetes.ServiceID]
serviceID, ok := m.Latest.Lookup(kubernetes.ServiceID)
if !ok {
return RenderableNodes{}
}
var (
id = MakeServiceID(serviceID)
major = m.Metadata[kubernetes.ServiceName]
rank = m.Metadata[kubernetes.ServiceID]
id = MakeServiceID(serviceID)
major, _ = m.Latest.Lookup(kubernetes.ServiceName)
rank, _ = m.Latest.Lookup(kubernetes.ServiceID)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
@@ -222,7 +222,7 @@ func MapServiceIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
// node. As it is only ever run on address topology nodes, we expect that
// certain keys are present.
func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes {
addr, ok := m.Metadata[endpoint.Addr]
addr, ok := m.Latest.Lookup(endpoint.Addr)
if !ok {
return RenderableNodes{}
}
@@ -230,8 +230,8 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes
// Conntracked connections don't have a host id unless
// they were merged with a procspied connection. Filter
// out those that weren't.
_, hasHostID := m.Metadata[report.HostNodeID]
_, conntracked := m.Metadata[endpoint.Conntracked]
_, hasHostID := m.Latest.Lookup(report.HostNodeID)
_, conntracked := m.Latest.Lookup(endpoint.Conntracked)
if !hasHostID && conntracked {
return RenderableNodes{}
}
@@ -269,7 +269,7 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes
func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
var (
id = MakeHostID(report.ExtractHostID(m.Node))
hostname = m.Metadata[host.HostName]
hostname, _ = m.Latest.Lookup(host.HostName)
parts = strings.SplitN(hostname, ".", 2)
major, minor, rank = "", "", ""
)
@@ -289,7 +289,7 @@ func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
// don't want to double count edges.
func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
// Don't include procspied connections, to prevent double counting
_, ok := m.Metadata[endpoint.Procspied]
_, ok := m.Latest.Lookup(endpoint.Procspied)
if ok {
return RenderableNodes{}
}
@@ -308,7 +308,7 @@ 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(nil)
m = m.WithParents(report.EmptySets)
return RenderableNodes{
id: NewRenderableNodeWith(id, "", "", "", m),
idWithPort: NewRenderableNodeWith(idWithPort, "", "", "", m),
@@ -322,7 +322,7 @@ var portMappingMatch = regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.
// the endpoint topology.
func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
result := RenderableNodes{}
if addrs, ok := m.Sets[docker.ContainerIPsWithScopes]; ok {
if addrs, ok := m.Sets.Lookup(docker.ContainerIPsWithScopes); ok {
for _, addr := range addrs {
scope, addr, ok := report.ParseAddressNodeID(addr)
if !ok {
@@ -330,19 +330,20 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
}
id := report.MakeScopedEndpointNodeID(scope, addr, "")
node := NewRenderableNodeWith(id, "", "", "", m)
node.Counters[containersKey] = 1
node.Counters = node.Counters.Add(containersKey, 1)
result[id] = node
}
}
// Also output all the host:port port mappings (see above comment).
// In this case we assume this doesn't need a scope, as they are for host IPs.
for _, portMapping := range m.Sets[docker.ContainerPorts] {
ports, _ := m.Sets.Lookup(docker.ContainerPorts)
for _, portMapping := range ports {
if mapping := portMappingMatch.FindStringSubmatch(portMapping); mapping != nil {
ip, port := mapping[1], mapping[2]
id := report.MakeScopedEndpointNodeID("", ip, port)
node := NewRenderableNodeWith(id, "", "", "", m.WithParents(nil))
node.Counters[containersKey] = 1
node := NewRenderableNodeWith(id, "", "", "", m.WithParents(report.EmptySets))
node.Counters = node.Counters.Add(containersKey, 1)
result[id] = node
}
}
@@ -356,7 +357,7 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
// If an IP is shared between multiple containers, we can't
// reliably attribute an connection based on its IP
if n.Node.Counters[containersKey] > 1 {
if count, _ := n.Node.Counters.Lookup(containersKey); count > 1 {
return RenderableNodes{}
}
@@ -368,14 +369,14 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
// 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.Metadata[docker.ContainerID]
containerID, ok := n.Node.Latest.Lookup(docker.ContainerID)
if !ok {
return RenderableNodes{}
}
id := MakeContainerID(containerID)
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(nil))}
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(report.EmptySets))}
}
// MapEndpoint2Process maps endpoint RenderableNodes to process
@@ -394,13 +395,13 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{n.ID: n}
}
pid, ok := n.Node.Metadata[process.PID]
pid, ok := n.Node.Latest.Lookup(process.PID)
if !ok {
return RenderableNodes{}
}
id := MakeProcessID(report.ExtractHostID(n.Node), pid)
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(nil))}
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(report.EmptySets))}
}
// MapProcess2Container maps process RenderableNodes to container
@@ -434,8 +435,8 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
node RenderableNode
hostID = report.ExtractHostID(n.Node)
)
n = n.WithParents(nil)
if containerID, ok := n.Node.Metadata[docker.ContainerID]; ok {
n = n.WithParents(report.EmptySets)
if containerID, ok := n.Node.Latest.Lookup(docker.ContainerID); ok {
id = MakeContainerID(containerID)
node = NewDerivedNode(id, n)
} else {
@@ -459,7 +460,7 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{n.ID: n}
}
name, ok := n.Node.Metadata[process.Name]
name, ok := n.Node.Latest.Lookup(process.Name)
if !ok {
return RenderableNodes{}
}
@@ -467,7 +468,7 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
node := NewDerivedNode(name, n)
node.LabelMajor = name
node.Rank = name
node.Node.Counters[processesKey] = 1
node.Counters = node.Node.Counters.Add(processesKey, 1)
node.Node.Topology = "process_name"
node.Node.ID = name
node.Children = node.Children.Add(n.Node)
@@ -482,7 +483,7 @@ func MapCountProcessName(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{n.ID: n}
}
processes := n.Node.Counters[processesKey]
processes, _ := n.Node.Counters.Lookup(processesKey)
if processes == 1 {
n.LabelMinor = "1 process"
} else {
@@ -510,15 +511,15 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
// 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.Metadata[docker.ImageID]
imageID, ok := n.Node.Latest.Lookup(docker.ImageID)
if !ok {
return RenderableNodes{}
}
// 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(nil))
result.Node.Counters[containersKey] = 1
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.Node)
@@ -546,7 +547,7 @@ func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes {
// 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.Metadata[kubernetes.ServiceIDs]
ids, ok := n.Node.Latest.Lookup(kubernetes.ServiceIDs)
if !ok {
return RenderableNodes{}
}
@@ -554,8 +555,8 @@ func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes {
result := RenderableNodes{}
for _, serviceID := range strings.Fields(ids) {
id := MakeServiceID(serviceID)
n := NewDerivedNode(id, n.WithParents(nil))
n.Node.Counters[podsKey] = 1
n := NewDerivedNode(id, n.WithParents(report.EmptySets))
n.Node.Counters = n.Node.Counters.Add(podsKey, 1)
n.Children = n.Children.Add(n.Node)
result[id] = n
}
@@ -584,7 +585,7 @@ func MapContainerImage2Name(n RenderableNode, _ report.Networks) RenderableNodes
return RenderableNodes{n.ID: n}
}
name, ok := n.Node.Metadata[docker.ImageName]
name, ok := n.Node.Latest.Lookup(docker.ImageName)
if !ok {
return RenderableNodes{}
}
@@ -614,11 +615,11 @@ func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes {
if n.Pseudo {
return RenderableNodes{n.ID: n}
}
if _, ok := n.Node.Metadata[report.HostNodeID]; !ok {
if _, ok := n.Node.Latest.Lookup(report.HostNodeID); !ok {
return RenderableNodes{}
}
id := MakeHostID(report.ExtractHostID(n.Node))
result := NewDerivedNode(id, n.WithParents(nil))
result := NewDerivedNode(id, n.WithParents(report.EmptySets))
result.Children = result.Children.Add(n.Node)
return RenderableNodes{id: result}
}
@@ -642,7 +643,7 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
// 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.Metadata[kubernetes.PodID]
podID, ok := n.Node.Latest.Lookup(kubernetes.PodID)
if !ok {
return RenderableNodes{}
}
@@ -650,15 +651,18 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
// Add container-<id> key to NMD, which will later be counted to produce the
// minor label
result := NewRenderableNodeWith(id, "", "", podID, n.WithParents(nil))
result.Node.Counters[containersKey] = 1
result := NewRenderableNodeWith(id, "", "", podID, n.WithParents(report.EmptySets))
result.Counters = result.Counters.Add(containersKey, 1)
// Due to a bug in kubernetes, addon pods on the master node are not returned
// from the API. This 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.Metadata[kubernetes.Namespace] = s[0]
result.Node.Metadata[kubernetes.PodName] = s[1]
result.Node = result.Node.WithLatests(map[string]string{
kubernetes.Namespace: s[0],
kubernetes.PodName: s[1],
})
}
result.Children = result.Children.Add(n.Node)
@@ -675,7 +679,7 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes
// 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.Metadata[docker.ContainerHostname]
id, ok := n.Node.Latest.Lookup(docker.ContainerHostname)
if !ok {
return RenderableNodes{}
}
@@ -685,7 +689,7 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes
result.Rank = id
// Add container id key to the counters, which will later be counted to produce the minor label
result.Node.Counters[containersKey] = 1
result.Counters = result.Counters.Add(containersKey, 1)
result.Node.Topology = "container_hostname"
result.Node.ID = id
@@ -703,7 +707,7 @@ func MapCountContainers(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{n.ID: n}
}
containers := n.Node.Counters[containersKey]
containers, _ := n.Node.Counters.Lookup(containersKey)
if containers == 1 {
n.LabelMinor = "1 container"
} else {
@@ -719,7 +723,7 @@ func MapCountPods(n RenderableNode, _ report.Networks) RenderableNodes {
return RenderableNodes{n.ID: n}
}
pods := n.Node.Counters[podsKey]
pods, _ := n.Node.Counters.Lookup(podsKey)
if pods == 1 {
n.LabelMinor = "1 pod"
} else {

1
render/report.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -27,14 +27,13 @@ func MakeRenderableNodes(t report.Topology) RenderableNodes {
// Push EdgeMetadata to both ends of the edges
for srcID, srcNode := range result {
for dstID, emd := range srcNode.Edges {
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

View File

@@ -33,13 +33,13 @@ var (
rpt = report.Report{
Endpoint: report.Topology{
Nodes: report.Nodes{
randomEndpointNodeID: report.MakeNode().WithMetadata(map[string]string{
randomEndpointNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: randomIP,
endpoint.Port: randomPort,
endpoint.Conntracked: "true",
}).WithAdjacent(serverEndpointNodeID).WithID(randomEndpointNodeID).WithTopology(report.Endpoint),
serverEndpointNodeID: report.MakeNode().WithMetadata(map[string]string{
serverEndpointNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: serverIP,
endpoint.Port: serverPort,
endpoint.Conntracked: "true",
@@ -48,23 +48,23 @@ var (
},
Container: report.Topology{
Nodes: report.Nodes{
containerNodeID: report.MakeNode().WithMetadata(map[string]string{
containerNodeID: report.MakeNode().WithLatests(map[string]string{
docker.ContainerID: containerID,
docker.ContainerName: containerName,
report.HostNodeID: serverHostNodeID,
}).WithSets(report.Sets{
docker.ContainerIPs: report.MakeStringSet(containerIP),
docker.ContainerPorts: report.MakeStringSet(fmt.Sprintf("%s:%s->%s/tcp", serverIP, serverPort, serverPort)),
}).WithID(containerNodeID).WithTopology(report.Container),
}).WithSets(report.EmptySets.
Add(docker.ContainerIPs, report.MakeStringSet(containerIP)).
Add(docker.ContainerPorts, report.MakeStringSet(fmt.Sprintf("%s:%s->%s/tcp", serverIP, serverPort, serverPort))),
).WithID(containerNodeID).WithTopology(report.Container),
},
},
Host: report.Topology{
Nodes: report.Nodes{
serverHostNodeID: report.MakeNodeWith(map[string]string{
report.HostNodeID: serverHostNodeID,
}).WithSets(report.Sets{
host.LocalNetworks: report.MakeStringSet("192.168.0.0/16"),
}).WithID(serverHostNodeID).WithTopology(report.Host),
}).WithSets(report.EmptySets.
Add(host.LocalNetworks, report.MakeStringSet("192.168.0.0/16")),
).WithID(serverHostNodeID).WithTopology(report.Host),
},
},
}

View File

@@ -18,7 +18,8 @@ func LocalNetworks(r report.Report) report.Networks {
)
for _, md := range r.Host.Nodes {
for _, s := range md.Sets[host.LocalNetworks] {
nets, _ := md.Sets.Lookup(host.LocalNetworks)
for _, s := range nets {
_, ipNet, err := net.ParseCIDR(s)
if err != nil {
continue

View File

@@ -16,10 +16,10 @@ func TestReportLocalNetworks(t *testing.T) {
Host: report.Topology{
Nodes: report.Nodes{
"nonets": report.MakeNode(),
"foo": report.MakeNode().WithSets(report.Sets{
host.LocalNetworks: report.MakeStringSet(
"10.0.0.1/8", "192.168.1.1/24", "10.0.0.1/8", "badnet/33"),
}),
"foo": report.MakeNode().WithSets(report.EmptySets.
Add(host.LocalNetworks, report.MakeStringSet(
"10.0.0.1/8", "192.168.1.1/24", "10.0.0.1/8", "badnet/33")),
),
},
},
})

View File

@@ -41,11 +41,11 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report) RenderableNo
}.Render(rpt)
for id, p := range processes {
pid, ok := p.Node.Metadata[process.PID]
pid, ok := p.Node.Latest.Lookup(process.PID)
if !ok {
continue
}
containerID, ok := p.Node.Metadata[docker.ContainerID]
containerID, ok := p.Node.Latest.Lookup(docker.ContainerID)
if !ok {
continue
}
@@ -82,8 +82,8 @@ var ProcessNameRenderer = Map{
var ContainerRenderer = MakeReduce(
Filter{
FilterFunc: func(n RenderableNode) bool {
_, inContainer := n.Node.Metadata[docker.ContainerID]
_, isConnected := n.Node.Metadata[IsConnected]
_, inContainer := n.Node.Latest.Lookup(docker.ContainerID)
_, isConnected := n.Node.Latest.Lookup(IsConnected)
return inContainer || isConnected
},
Renderer: Map{
@@ -131,7 +131,7 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report) RenderableNode
}.Render(rpt)
for id, c := range containers {
imageID, ok := c.Node.Metadata[docker.ImageID]
imageID, ok := c.Node.Latest.Lookup(docker.ImageID)
if !ok {
continue
}
@@ -140,7 +140,7 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report) RenderableNode
continue
}
c.Rank = ImageNameWithoutVersion(image.LabelMajor)
c.Metadata = image.Metadata.Merge(c.Metadata)
c.Latest = c.Latest.Merge(c.Latest)
containers[id] = c
}

View File

@@ -40,7 +40,9 @@ func TestContainerFilterRenderer(t *testing.T) {
// tag on of the containers in the topology and ensure
// it is filtered out correctly.
input := fixture.Report.Copy()
input.Container.Nodes[fixture.ClientContainerNodeID].Metadata[docker.LabelPrefix+"works.weave.role"] = "system"
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()
want := expected.RenderedContainers.Copy()
delete(want, expected.ClientContainerRenderedID)
@@ -77,10 +79,14 @@ func TestPodFilterRenderer(t *testing.T) {
// tag on containers or pod namespace in the topology and ensure
// it is filtered out correctly.
input := fixture.Report.Copy()
input.Pod.Nodes[fixture.ClientPodNodeID].Metadata[kubernetes.PodID] = "pod:kube-system/foo"
input.Pod.Nodes[fixture.ClientPodNodeID].Metadata[kubernetes.Namespace] = "kube-system"
input.Pod.Nodes[fixture.ClientPodNodeID].Metadata[kubernetes.PodName] = "foo"
input.Container.Nodes[fixture.ClientContainerNodeID].Metadata[docker.LabelPrefix+"io.kubernetes.pod.name"] = "kube-system/foo"
input.Pod.Nodes[fixture.ClientPodNodeID] = input.Pod.Nodes[fixture.ClientPodNodeID].WithLatests(map[string]string{
kubernetes.PodID: "pod:kube-system/foo",
kubernetes.Namespace: "kube-system",
kubernetes.PodName: "foo",
})
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
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)

187
report/counters.go Normal file
View File

@@ -0,0 +1,187 @@
package report
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"reflect"
"sort"
"github.com/mndrix/ps"
)
// Counters is a string->int map.
type Counters struct {
psMap ps.Map
}
// EmptyCounters is the set of empty counters.
var EmptyCounters = Counters{ps.NewMap()}
// MakeCounters returns EmptyCounters
func MakeCounters() Counters {
return EmptyCounters
}
// Copy is a noop
func (c Counters) Copy() Counters {
return c
}
// Add value to the counter 'key'
func (c Counters) Add(key string, value int) Counters {
if c.psMap == nil {
c = EmptyCounters
}
if existingValue, ok := c.psMap.Lookup(key); ok {
value += existingValue.(int)
}
return Counters{
c.psMap.Set(key, value),
}
}
// Lookup the counter 'key'
func (c Counters) Lookup(key string) (int, bool) {
if c.psMap != nil {
existingValue, ok := c.psMap.Lookup(key)
if ok {
return existingValue.(int), true
}
}
return 0, false
}
// Size returns the number of counters
func (c Counters) Size() int {
if c.psMap == nil {
return 0
}
return c.psMap.Size()
}
// Merge produces a fresh Counters, container the keys from both inputs. When
// both inputs container the same key, the latter value is used.
func (c Counters) Merge(other Counters) Counters {
var (
cSize = c.Size()
otherSize = other.Size()
output = c.psMap
iter = other.psMap
)
switch {
case cSize == 0:
return other
case otherSize == 0:
return c
case cSize < otherSize:
output, iter = iter, output
}
iter.ForEach(func(key string, otherVal interface{}) {
if val, ok := output.Lookup(key); ok {
output = output.Set(key, otherVal.(int)+val.(int))
} else {
output = output.Set(key, otherVal)
}
})
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)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %d, ", key, val)
}
fmt.Fprintf(buf, "}\n")
return buf.String()
}
// DeepEqual tests equality with other Counters
func (c Counters) DeepEqual(i interface{}) bool {
d, ok := i.(Counters)
if !ok {
return false
}
if (c.psMap == nil) != (d.psMap == nil) {
return false
} else if c.psMap == nil && d.psMap == nil {
return true
}
if c.psMap.Size() != d.psMap.Size() {
return false
}
equal := true
c.psMap.ForEach(func(k string, val interface{}) {
if otherValue, ok := d.psMap.Lookup(k); !ok {
equal = false
} else {
equal = equal && reflect.DeepEqual(val, otherValue)
}
})
return equal
}
func (c Counters) toIntermediate() map[string]int {
intermediate := map[string]int{}
c.psMap.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(int)
})
return intermediate
}
func (c Counters) fromIntermediate(in map[string]int) Counters {
out := ps.NewMap()
for k, v := range in {
out = out.Set(k, v)
}
return Counters{out}
}
// MarshalJSON implements json.Marshaller
func (c Counters) MarshalJSON() ([]byte, error) {
if c.psMap != nil {
return json.Marshal(c.toIntermediate())
}
return json.Marshal(nil)
}
// UnmarshalJSON implements json.Unmarshaler
func (c *Counters) UnmarshalJSON(input []byte) error {
in := map[string]int{}
if err := json.Unmarshal(input, &in); err != nil {
return err
}
*c = Counters{}.fromIntermediate(in)
return nil
}
// GobEncode implements gob.Marshaller
func (c Counters) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(c.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (c *Counters) GobDecode(input []byte) error {
in := map[string]int{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*c = Counters{}.fromIntermediate(in)
return nil
}

View File

@@ -0,0 +1,124 @@
package report
import (
"testing"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func TestCountersAdd(t *testing.T) {
have := EmptyCounters.
Add("foo", 1).
Add("foo", 2)
if v, ok := have.Lookup("foo"); !ok || v != 3 {
t.Errorf("foo != 3")
}
if v, ok := have.Lookup("bar"); ok || v != 0 {
t.Errorf("bar != nil")
}
}
func TestCountersDeepEquals(t *testing.T) {
want := EmptyCounters.
Add("foo", 3)
have := EmptyCounters.
Add("foo", 3)
if !reflect.DeepEqual(want, have) {
t.Errorf(test.Diff(want, have))
}
notequal := EmptyCounters.
Add("foo", 4)
if reflect.DeepEqual(want, notequal) {
t.Errorf(test.Diff(want, have))
}
}
func TestCountersNil(t *testing.T) {
want := Counters{}
if want.Size() != 0 {
t.Errorf("nil.Size != 0")
}
if v, ok := want.Lookup("foo"); ok || v != 0 {
t.Errorf("nil.Lookup != false")
}
have := want.Add("foo", 1)
if v, ok := have.Lookup("foo"); !ok || v != 1 {
t.Errorf("nil.Add failed")
}
if have2 := want.Merge(have); !reflect.DeepEqual(have, have2) {
t.Errorf(test.Diff(have, have2))
}
}
func TestCountersMerge(t *testing.T) {
for name, c := range map[string]struct {
a, b, want Counters
}{
"Empty a": {
a: EmptyCounters,
b: EmptyCounters.
Add("foo", 1),
want: EmptyCounters.
Add("foo", 1),
},
"Empty b": {
a: EmptyCounters.
Add("foo", 1),
b: EmptyCounters,
want: EmptyCounters.
Add("foo", 1),
},
"Disjoin a & b": {
a: EmptyCounters.
Add("foo", 1),
b: EmptyCounters.
Add("bar", 2),
want: EmptyCounters.
Add("foo", 1).
Add("bar", 2),
},
"Overlapping a & b": {
a: EmptyCounters.
Add("foo", 1),
b: EmptyCounters.
Add("foo", 2),
want: EmptyCounters.
Add("foo", 3),
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s:\n%s", name, test.Diff(c.want, have))
}
}
}
func TestCountersEncoding(t *testing.T) {
want := EmptyCounters.
Add("foo", 1).
Add("bar", 2)
{
gobs, err := want.GobEncode()
if err != nil {
t.Fatal(err)
}
have := EmptyCounters
have.GobDecode(gobs)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
json, err := want.MarshalJSON()
if err != nil {
t.Fatal(err)
}
have := EmptyCounters
have.UnmarshalJSON(json)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}

296
report/edge_metadatas.go Normal file
View File

@@ -0,0 +1,296 @@
package report
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"reflect"
"sort"
"github.com/mndrix/ps"
)
// EdgeMetadatas collect metadata about each edge in a topology. Keys are the
// remote node IDs, as in Adjacency.
type EdgeMetadatas struct {
psMap ps.Map
}
// EmptyEdgeMetadatas is the set of empty EdgeMetadatas.
var EmptyEdgeMetadatas = EdgeMetadatas{ps.NewMap()}
// MakeEdgeMetadatas returns EmptyEdgeMetadatas
func MakeEdgeMetadatas() EdgeMetadatas {
return EmptyEdgeMetadatas
}
// Copy is a noop
func (c EdgeMetadatas) Copy() EdgeMetadatas {
return c
}
// Add value to the counter 'key'
func (c EdgeMetadatas) Add(key string, value EdgeMetadata) EdgeMetadatas {
if c.psMap == nil {
c = EmptyEdgeMetadatas
}
if existingValue, ok := c.psMap.Lookup(key); ok {
value = value.Merge(existingValue.(EdgeMetadata))
}
return EdgeMetadatas{
c.psMap.Set(key, value),
}
}
// Lookup the counter 'key'
func (c EdgeMetadatas) Lookup(key string) (EdgeMetadata, bool) {
if c.psMap != nil {
existingValue, ok := c.psMap.Lookup(key)
if ok {
return existingValue.(EdgeMetadata), true
}
}
return EdgeMetadata{}, false
}
// Size is the number of elements
func (c EdgeMetadatas) Size() int {
if c.psMap == nil {
return 0
}
return c.psMap.Size()
}
// Merge produces a fresh Counters, container the keys from both inputs. When
// both inputs container the same key, the latter value is used.
func (c EdgeMetadatas) Merge(other EdgeMetadatas) EdgeMetadatas {
var (
cSize = c.Size()
otherSize = other.Size()
output = c.psMap
iter = other.psMap
)
switch {
case cSize == 0:
return other
case otherSize == 0:
return c
case cSize < otherSize:
output, iter = iter, output
}
iter.ForEach(func(key string, otherVal interface{}) {
if val, ok := output.Lookup(key); ok {
output = output.Set(key, otherVal.(EdgeMetadata).Merge(val.(EdgeMetadata)))
} else {
output = output.Set(key, otherVal)
}
})
return EdgeMetadatas{output}
}
// Flatten flattens all the EdgeMetadatas in this set and returns the result.
// The original is not modified.
func (c EdgeMetadatas) Flatten() EdgeMetadata {
result := EdgeMetadata{}
c.ForEach(func(_ string, e EdgeMetadata) {
result = result.Flatten(e)
})
return result
}
// ForEach executes f on each key value pair in the map
func (c EdgeMetadatas) ForEach(fn func(k string, v EdgeMetadata)) {
if c.psMap != nil {
c.psMap.ForEach(func(key string, value interface{}) {
fn(key, value.(EdgeMetadata))
})
}
}
func (c EdgeMetadatas) String() string {
keys := []string{}
if c.psMap == nil {
c = EmptyEdgeMetadatas
}
for _, k := range c.psMap.Keys() {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
fmt.Fprintf(buf, "}\n")
return buf.String()
}
// DeepEqual tests equality with other Counters
func (c EdgeMetadatas) DeepEqual(i interface{}) bool {
d, ok := i.(EdgeMetadatas)
if !ok {
return false
}
if c.Size() != d.Size() {
return false
}
if c.Size() == 0 {
return true
}
equal := true
c.psMap.ForEach(func(k string, val interface{}) {
if otherValue, ok := d.psMap.Lookup(k); !ok {
equal = false
} else {
equal = equal && reflect.DeepEqual(val, otherValue)
}
})
return equal
}
func (c EdgeMetadatas) toIntermediate() map[string]EdgeMetadata {
intermediate := map[string]EdgeMetadata{}
if c.psMap != nil {
c.psMap.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(EdgeMetadata)
})
}
return intermediate
}
func (c EdgeMetadatas) fromIntermediate(in map[string]EdgeMetadata) EdgeMetadatas {
out := ps.NewMap()
for k, v := range in {
out = out.Set(k, v)
}
return EdgeMetadatas{out}
}
// MarshalJSON implements json.Marshaller
func (c EdgeMetadatas) MarshalJSON() ([]byte, error) {
if c.psMap != nil {
return json.Marshal(c.toIntermediate())
}
return json.Marshal(nil)
}
// UnmarshalJSON implements json.Unmarshaler
func (c *EdgeMetadatas) UnmarshalJSON(input []byte) error {
in := map[string]EdgeMetadata{}
if err := json.Unmarshal(input, &in); err != nil {
return err
}
*c = EdgeMetadatas{}.fromIntermediate(in)
return nil
}
// GobEncode implements gob.Marshaller
func (c EdgeMetadatas) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(c.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (c *EdgeMetadatas) GobDecode(input []byte) error {
in := map[string]EdgeMetadata{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*c = EdgeMetadatas{}.fromIntermediate(in)
return nil
}
// EdgeMetadata describes a superset of the metadata that probes can possibly
// collect about a directed edge between two nodes in any topology.
type EdgeMetadata struct {
EgressPacketCount *uint64 `json:"egress_packet_count,omitempty"`
IngressPacketCount *uint64 `json:"ingress_packet_count,omitempty"`
EgressByteCount *uint64 `json:"egress_byte_count,omitempty"` // Transport layer
IngressByteCount *uint64 `json:"ingress_byte_count,omitempty"` // Transport layer
MaxConnCountTCP *uint64 `json:"max_conn_count_tcp,omitempty"`
}
// Copy returns a value copy of the EdgeMetadata.
func (e EdgeMetadata) Copy() EdgeMetadata {
return EdgeMetadata{
EgressPacketCount: cpu64ptr(e.EgressPacketCount),
IngressPacketCount: cpu64ptr(e.IngressPacketCount),
EgressByteCount: cpu64ptr(e.EgressByteCount),
IngressByteCount: cpu64ptr(e.IngressByteCount),
MaxConnCountTCP: cpu64ptr(e.MaxConnCountTCP),
}
}
// Reversed returns a value copy of the EdgeMetadata, with the direction reversed.
func (e EdgeMetadata) Reversed() EdgeMetadata {
return EdgeMetadata{
EgressPacketCount: cpu64ptr(e.IngressPacketCount),
IngressPacketCount: cpu64ptr(e.EgressPacketCount),
EgressByteCount: cpu64ptr(e.IngressByteCount),
IngressByteCount: cpu64ptr(e.EgressByteCount),
MaxConnCountTCP: cpu64ptr(e.MaxConnCountTCP),
}
}
func cpu64ptr(u *uint64) *uint64 {
if u == nil {
return nil
}
value := *u // oh man
return &value // this sucks
}
// Merge merges another EdgeMetadata into the receiver and returns the result.
// The receiver is not modified. The two edge metadatas should represent the
// same edge on different times.
func (e EdgeMetadata) Merge(other EdgeMetadata) EdgeMetadata {
cp := e.Copy()
cp.EgressPacketCount = merge(cp.EgressPacketCount, other.EgressPacketCount, sum)
cp.IngressPacketCount = merge(cp.IngressPacketCount, other.IngressPacketCount, sum)
cp.EgressByteCount = merge(cp.EgressByteCount, other.EgressByteCount, sum)
cp.IngressByteCount = merge(cp.IngressByteCount, other.IngressByteCount, sum)
cp.MaxConnCountTCP = merge(cp.MaxConnCountTCP, other.MaxConnCountTCP, max)
return cp
}
// Flatten sums two EdgeMetadatas and returns the result. The receiver is not
// modified. The two edge metadata windows should be the same duration; they
// should represent different edges at the same time.
func (e EdgeMetadata) Flatten(other EdgeMetadata) EdgeMetadata {
cp := e.Copy()
cp.EgressPacketCount = merge(cp.EgressPacketCount, other.EgressPacketCount, sum)
cp.IngressPacketCount = merge(cp.IngressPacketCount, other.IngressPacketCount, sum)
cp.EgressByteCount = merge(cp.EgressByteCount, other.EgressByteCount, sum)
cp.IngressByteCount = merge(cp.IngressByteCount, other.IngressByteCount, sum)
// Note that summing of two maximums doesn't always give us the true
// maximum. But it's a best effort.
cp.MaxConnCountTCP = merge(cp.MaxConnCountTCP, other.MaxConnCountTCP, sum)
return cp
}
func merge(dst, src *uint64, op func(uint64, uint64) uint64) *uint64 {
if src == nil {
return dst
}
if dst == nil {
dst = new(uint64)
}
(*dst) = op(*dst, *src)
return dst
}
func sum(dst, src uint64) uint64 {
return dst + src
}
func max(dst, src uint64) uint64 {
if dst > src {
return dst
}
return src
}

View File

@@ -0,0 +1,284 @@
package report
import (
"testing"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func TestEdgeMetadatasAdd(t *testing.T) {
have := EmptyEdgeMetadatas.
Add("foo",
EdgeMetadata{
EgressPacketCount: newu64(1),
}).
Add("foo",
EdgeMetadata{
EgressPacketCount: newu64(2),
})
if emd, ok := have.Lookup("foo"); !ok || *emd.EgressPacketCount != 3 {
t.Errorf("foo.EgressPacketCount != 3")
}
if emd, ok := have.Lookup("bar"); ok || emd.EgressPacketCount != nil {
t.Errorf("bar.EgressPacketCount != nil")
}
have.ForEach(func(k string, emd EdgeMetadata) {
if k != "foo" || *emd.EgressPacketCount != 3 {
t.Errorf("foo.EgressPacketCount != 3")
}
})
}
func TestEdgeMetadatasAddNil(t *testing.T) {
have := EdgeMetadatas{}.Add("foo", EdgeMetadata{EgressPacketCount: newu64(1)})
if have.Size() != 1 {
t.Errorf("Adding to a zero-value EdgeMetadatas failed, got: %v", have)
}
}
func TestEdgeMetadatasDeepEquals(t *testing.T) {
want := EmptyEdgeMetadatas.
Add("foo",
EdgeMetadata{
EgressPacketCount: newu64(3),
})
have := EmptyEdgeMetadatas.
Add("foo",
EdgeMetadata{
EgressPacketCount: newu64(3),
})
if !reflect.DeepEqual(want, have) {
t.Errorf(test.Diff(want, have))
}
}
func TestEdgeMetadatasMerge(t *testing.T) {
for name, c := range map[string]struct {
a, b, want EdgeMetadatas
}{
"nils": {
a: EdgeMetadatas{},
b: EdgeMetadatas{},
want: EdgeMetadatas{},
},
"Empty a": {
a: EmptyEdgeMetadatas,
b: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}),
want: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}),
},
"Empty b": {
a: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
}),
b: EmptyEdgeMetadatas,
want: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
}),
},
"Disjoint a & b": {
a: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
}),
b: EmptyEdgeMetadatas.
Add("hostQ|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
}),
want: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
}).
Add("hostQ|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
}),
},
"Overlapping a & b": {
a: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(1000),
MaxConnCountTCP: newu64(7),
}),
b: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(1),
IngressByteCount: newu64(123),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(9),
}),
want: EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
EdgeMetadata{
EgressPacketCount: newu64(13),
IngressByteCount: newu64(123),
EgressByteCount: newu64(1002),
MaxConnCountTCP: newu64(9),
}),
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s:\n%s", name, test.Diff(c.want, have))
}
}
}
func TestEdgeMetadataFlatten(t *testing.T) {
// Test two EdgeMetadatas flatten to the correct values
{
have := (EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}).Flatten(EdgeMetadata{
EgressPacketCount: newu64(4),
EgressByteCount: newu64(8),
MaxConnCountTCP: newu64(16),
})
want := EdgeMetadata{
EgressPacketCount: newu64(1 + 4),
EgressByteCount: newu64(8),
MaxConnCountTCP: newu64(2 + 16), // flatten should sum MaxConnCountTCP
}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
// Test an EdgeMetadatas flatten to the correct value (should
// just sum)
{
have := EmptyEdgeMetadatas.
Add("foo", EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}).
Add("bar", EdgeMetadata{
EgressPacketCount: newu64(3),
MaxConnCountTCP: newu64(5),
}).Flatten()
want := EdgeMetadata{
EgressPacketCount: newu64(1 + 3),
MaxConnCountTCP: newu64(2 + 5),
}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
// Should not panic on nil
have := EdgeMetadatas{}.Flatten()
want := EdgeMetadata{}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}
func TestEdgeMetadataReversed(t *testing.T) {
have := EdgeMetadata{
EgressPacketCount: newu64(1),
}.Reversed()
want := EdgeMetadata{
IngressPacketCount: newu64(1),
}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestEdgeMetadatasEncoding(t *testing.T) {
want := EmptyEdgeMetadatas.
Add("foo", EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}).
Add("bar", EdgeMetadata{
EgressPacketCount: newu64(3),
MaxConnCountTCP: newu64(5),
})
{
gobs, err := want.GobEncode()
if err != nil {
t.Fatal(err)
}
have := EmptyEdgeMetadatas
have.GobDecode(gobs)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
json, err := want.MarshalJSON()
if err != nil {
t.Fatal(err)
}
have := EmptyEdgeMetadatas
have.UnmarshalJSON(json)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}
func TestEdgeMetadatasEncodingNil(t *testing.T) {
want := EdgeMetadatas{}
{
gobs, err := want.GobEncode()
if err != nil {
t.Fatal(err)
}
have := EmptyEdgeMetadatas
have.GobDecode(gobs)
if have.psMap == nil {
t.Error("needed to get back a non-nil psMap for EdgeMetadata")
}
}
{
json, err := want.MarshalJSON()
if err != nil {
t.Fatal(err)
}
have := EmptyEdgeMetadatas
have.UnmarshalJSON(json)
if have.psMap == nil {
t.Error("needed to get back a non-nil psMap for EdgeMetadata")
}
}
}
func newu64(value uint64) *uint64 { return &value }

View File

@@ -168,8 +168,9 @@ func ParseAddressNodeID(addressNodeID string) (hostID, address string, ok bool)
// ExtractHostID extracts the host id from Node
func ExtractHostID(m Node) string {
hostid, _, _ := ParseNodeID(m.Metadata[HostNodeID])
return hostid
hostNodeID, _ := m.Latest.Lookup(HostNodeID)
hostID, _, _ := ParseNodeID(hostNodeID)
return hostID
}
func isLoopback(address string) bool {

View File

@@ -5,6 +5,9 @@ import "sort"
// IDList is a list of string IDs, which are always sorted and unique.
type IDList StringSet
// EmptyIDList is an Empty ID List.
var EmptyIDList = IDList(EmptyStringSet)
// MakeIDList makes a new IDList.
func MakeIDList(ids ...string) IDList {
return IDList(MakeStringSet(ids...))

View File

@@ -1,10 +1,10 @@
package report_test
import (
"reflect"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
func TestIDList(t *testing.T) {

View File

@@ -5,6 +5,7 @@ import (
"encoding/gob"
"encoding/json"
"fmt"
"sort"
"time"
"github.com/mndrix/ps"
@@ -23,12 +24,20 @@ type LatestEntry struct {
}
func (e LatestEntry) String() string {
return fmt.Sprintf("\"%s\" (%d)", e.Value, e.Timestamp)
return fmt.Sprintf("\"%s\" (%s)", e.Value, e.Timestamp.String())
}
// Equal returns true if the supplied LatestEntry is equal to this one.
func (e LatestEntry) Equal(e2 LatestEntry) bool {
return e.Timestamp.Equal(e2.Timestamp) && e.Value == e2.Value
}
// EmptyLatestMap is an empty LatestMap. Start with this.
var EmptyLatestMap = LatestMap{ps.NewMap()}
// MakeLatestMap makes an empty LatestMap
func MakeLatestMap() LatestMap {
return LatestMap{ps.NewMap()}
return EmptyLatestMap
}
// Copy is a noop, as LatestMaps are immutable.
@@ -36,21 +45,39 @@ func (m LatestMap) Copy() LatestMap {
return m
}
// Size returns the number of elements
func (m LatestMap) Size() int {
if m.Map == nil {
return 0
}
return m.Map.Size()
}
// Merge produces a fresh LatestMap, container the kers from both inputs. When
// both inputs container the same key, the latter value is used.
func (m LatestMap) Merge(newer LatestMap) LatestMap {
// expect people to do old.Merge(new), optimise for that.
// ie if you do {k: v}.Merge({k: v'}), we end up just returning
// newer, unmodified.
output := newer.Map
func (m LatestMap) Merge(other LatestMap) LatestMap {
var (
mSize = m.Size()
otherSize = other.Size()
output = m.Map
iter = other.Map
)
switch {
case mSize == 0:
return other
case otherSize == 0:
return m
case mSize < otherSize:
output, iter = iter, output
}
m.Map.ForEach(func(key string, olderVal interface{}) {
if newerVal, ok := newer.Map.Lookup(key); ok {
if newerVal.(LatestEntry).Timestamp.Before(olderVal.(LatestEntry).Timestamp) {
output = output.Set(key, olderVal)
iter.ForEach(func(key string, iterVal interface{}) {
if existingVal, ok := output.Lookup(key); ok {
if existingVal.(LatestEntry).Timestamp.Before(iterVal.(LatestEntry).Timestamp) {
output = output.Set(key, iterVal)
}
} else {
output = output.Set(key, olderVal)
output = output.Set(key, iterVal)
}
})
@@ -59,6 +86,9 @@ func (m LatestMap) Merge(newer LatestMap) LatestMap {
// Lookup the value for the given key.
func (m LatestMap) Lookup(key string) (string, bool) {
if m.Map == nil {
return "", false
}
value, ok := m.Map.Lookup(key)
if !ok {
return "", false
@@ -68,14 +98,81 @@ func (m LatestMap) Lookup(key string) (string, bool) {
// Set the value for the given key.
func (m LatestMap) Set(key string, timestamp time.Time, value string) LatestMap {
if m.Map == nil {
m = EmptyLatestMap
}
return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value})}
}
// Delete the value for the given key.
func (m LatestMap) Delete(key string) LatestMap {
if m.Map == nil {
m = EmptyLatestMap
}
return LatestMap{m.Map.Delete(key)}
}
// ForEach executes f on each key value pair in the map
func (m LatestMap) ForEach(fn func(k, v string)) {
if m.Map == nil {
return
}
m.Map.ForEach(func(key string, value interface{}) {
fn(key, value.(LatestEntry).Value)
})
}
func (m LatestMap) String() string {
keys := []string{}
if m.Map == nil {
m = EmptyLatestMap
}
for _, k := range m.Map.Keys() {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := m.Map.Lookup(key)
fmt.Fprintf(buf, "%s: %s, ", key, val)
}
fmt.Fprintf(buf, "}\n")
return buf.String()
}
// DeepEqual tests equality with other LatestMap
func (m LatestMap) DeepEqual(i interface{}) bool {
n, ok := i.(LatestMap)
if !ok {
return false
}
if m.Size() != n.Size() {
return false
}
if m.Size() == 0 {
return true
}
equal := true
m.Map.ForEach(func(k string, val interface{}) {
if otherValue, ok := n.Map.Lookup(k); !ok {
equal = false
} else {
equal = equal && val.(LatestEntry).Equal(otherValue.(LatestEntry))
}
})
return equal
}
func (m LatestMap) toIntermediate() map[string]LatestEntry {
intermediate := map[string]LatestEntry{}
m.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(LatestEntry)
})
if m.Map != nil {
m.Map.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(LatestEntry)
})
}
return intermediate
}

View File

@@ -0,0 +1,179 @@
package report
import (
"testing"
"time"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func TestLatestMapAdd(t *testing.T) {
now := time.Now()
have := EmptyLatestMap.
Set("foo", now.Add(-1), "Baz").
Set("foo", now, "Bar")
if v, ok := have.Lookup("foo"); !ok || v != "Bar" {
t.Errorf("v != Bar")
}
if v, ok := have.Lookup("bar"); ok || v != "" {
t.Errorf("v != nil")
}
have.ForEach(func(k, v string) {
if k != "foo" || v != "Bar" {
t.Errorf("v != Bar")
}
})
}
func TestLatestMapAddNil(t *testing.T) {
now := time.Now()
have := LatestMap{}.Set("foo", now, "Bar")
if v, ok := have.Lookup("foo"); !ok || v != "Bar" {
t.Errorf("v != Bar")
}
}
func TestLatestMapDeepEquals(t *testing.T) {
now := time.Now()
want := EmptyLatestMap.
Set("foo", now, "Bar")
have := EmptyLatestMap.
Set("foo", now, "Bar")
if !reflect.DeepEqual(want, have) {
t.Errorf(test.Diff(want, have))
}
notequal := EmptyLatestMap.
Set("foo", now, "Baz")
if reflect.DeepEqual(want, notequal) {
t.Errorf(test.Diff(want, have))
}
}
func TestLatestMapDelete(t *testing.T) {
now := time.Now()
want := EmptyLatestMap
have := EmptyLatestMap.
Set("foo", now, "Baz").
Delete("foo")
if !reflect.DeepEqual(want, have) {
t.Errorf(test.Diff(want, have))
}
}
func TestLatestMapDeleteNil(t *testing.T) {
want := LatestMap{}
have := LatestMap{}.Delete("foo")
if !reflect.DeepEqual(want, have) {
t.Errorf(test.Diff(want, have))
}
}
func TestLatestMapMerge(t *testing.T) {
now := time.Now()
then := now.Add(-1)
for name, c := range map[string]struct {
a, b, want LatestMap
}{
"nils": {
a: LatestMap{},
b: LatestMap{},
want: LatestMap{},
},
"Empty a": {
a: EmptyLatestMap,
b: EmptyLatestMap.
Set("foo", now, "bar"),
want: EmptyLatestMap.
Set("foo", now, "bar"),
},
"Empty b": {
a: EmptyLatestMap.
Set("foo", now, "bar"),
b: EmptyLatestMap,
want: EmptyLatestMap.
Set("foo", now, "bar"),
},
"Disjoint a & b": {
a: EmptyLatestMap.
Set("foo", now, "bar"),
b: EmptyLatestMap.
Set("baz", now, "bop"),
want: EmptyLatestMap.
Set("foo", now, "bar").
Set("baz", now, "bop"),
},
"Common a & b": {
a: EmptyLatestMap.
Set("foo", now, "bar"),
b: EmptyLatestMap.
Set("foo", then, "baz"),
want: EmptyLatestMap.
Set("foo", now, "bar"),
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s:\n%s", name, test.Diff(c.want, have))
}
}
}
func TestLatestMapEncoding(t *testing.T) {
now := time.Now()
want := EmptyLatestMap.
Set("foo", now, "bar").
Set("bar", now, "baz")
{
gobs, err := want.GobEncode()
if err != nil {
t.Fatal(err)
}
have := EmptyLatestMap
have.GobDecode(gobs)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
json, err := want.MarshalJSON()
if err != nil {
t.Fatal(err)
}
have := EmptyLatestMap
have.UnmarshalJSON(json)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}
func TestLatestMapEncodingNil(t *testing.T) {
want := LatestMap{}
{
gobs, err := want.GobEncode()
if err != nil {
t.Fatal(err)
}
have := EmptyLatestMap
have.GobDecode(gobs)
if have.Map == nil {
t.Error("Decoded LatestMap.psMap should not be nil")
}
}
{
json, err := want.MarshalJSON()
if err != nil {
t.Fatal(err)
}
have := EmptyLatestMap
have.UnmarshalJSON(json)
if have.Map == nil {
t.Error("Decoded LatestMap.psMap should not be nil")
}
}
}

View File

@@ -1,273 +0,0 @@
package report_test
import (
"reflect"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
const (
PID = "pid"
Name = "name"
Domain = "domain"
)
func TestMergeEdgeMetadatas(t *testing.T) {
for name, c := range map[string]struct {
a, b, want report.EdgeMetadatas
}{
"Empty a": {
a: report.EdgeMetadatas{},
b: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
},
},
},
"Empty b": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
},
},
b: report.EdgeMetadatas{},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
},
},
},
"Host merge": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
},
},
b: report.EdgeMetadatas{
"hostQ|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
},
"hostQ|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
},
},
},
"Edge merge": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(1000),
MaxConnCountTCP: newu64(7),
},
},
b: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(1),
IngressByteCount: newu64(123),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(9),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
EgressPacketCount: newu64(13),
IngressByteCount: newu64(123),
EgressByteCount: newu64(1002),
MaxConnCountTCP: newu64(9),
},
},
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s:\n%s", name, test.Diff(c.want, have))
}
}
}
func TestFlattenEdgeMetadata(t *testing.T) {
have := (report.EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
}).Flatten(report.EdgeMetadata{
EgressPacketCount: newu64(4),
EgressByteCount: newu64(8),
MaxConnCountTCP: newu64(16),
})
want := report.EdgeMetadata{
EgressPacketCount: newu64(1 + 4),
EgressByteCount: newu64(8),
MaxConnCountTCP: newu64(2 + 16), // flatten should sum MaxConnCountTCP
}
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestMergeNodes(t *testing.T) {
for name, c := range map[string]struct {
a, b, want report.Nodes
}{
"Empty a": {
a: report.Nodes{},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Empty b": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Simple merge": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.2:12345": report.MakeNodeWith(map[string]string{
PID: "42",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
":192.168.1.2:12345": report.MakeNodeWith(map[string]string{
PID: "42",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Merge conflict with rank difference": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{ // <-- same ID
PID: "0",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Merge conflict with no rank difference": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{ // <-- same ID
PID: "0",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Counters": {
a: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 13,
"b": 57,
"c": 89,
}),
},
b: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 78,
"b": 3,
"d": 47,
}),
},
want: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 91,
"b": 60,
"c": 89,
"d": 47,
}),
},
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s: want\n\t%#v, have\n\t%#v", name, c.want, have)
}
}
}
func newu64(value uint64) *uint64 { return &value }

View File

@@ -4,12 +4,12 @@ import (
"bytes"
"encoding/gob"
"encoding/json"
"reflect"
"testing"
"time"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func TestMetricsMerge(t *testing.T) {

View File

@@ -2,11 +2,11 @@ package report_test
import (
"net"
"reflect"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func TestContains(t *testing.T) {

190
report/node.go Normal file
View File

@@ -0,0 +1,190 @@
package report
import (
"time"
"github.com/weaveworks/scope/common/mtime"
)
// Node describes a superset of the metadata that probes can collect about a
// given node in a given topology, along with the edges emanating from the
// node and metadata about those edges.
type Node struct {
ID string `json:"id,omitempty"`
Topology string `json:"topology,omitempty"`
Counters Counters `json:"counters,omitempty"`
Sets Sets `json:"sets,omitempty"`
Adjacency IDList `json:"adjacency"`
Edges EdgeMetadatas `json:"edges,omitempty"`
Controls NodeControls `json:"controls,omitempty"`
Latest LatestMap `json:"latest,omitempty"`
Metrics Metrics `json:"metrics,omitempty"`
Parents Sets `json:"parents,omitempty"`
}
// MakeNode creates a new Node with no initial metadata.
func MakeNode() Node {
return Node{
Counters: EmptyCounters,
Sets: EmptySets,
Adjacency: EmptyIDList,
Edges: EmptyEdgeMetadatas,
Controls: MakeNodeControls(),
Latest: EmptyLatestMap,
Metrics: Metrics{},
Parents: EmptySets,
}
}
// MakeNodeWith creates a new Node with the supplied map.
func MakeNodeWith(m map[string]string) Node {
return MakeNode().WithLatests(m)
}
// WithID returns a fresh copy of n, with ID changed.
func (n Node) WithID(id string) Node {
result := n.Copy()
result.ID = id
return result
}
// WithTopology returns a fresh copy of n, with ID changed.
func (n Node) WithTopology(topology string) Node {
result := n.Copy()
result.Topology = topology
return result
}
// Before is used for sorting nodes by topology and id
func (n Node) Before(other Node) bool {
return n.Topology < other.Topology || (n.Topology == other.Topology && n.ID < other.ID)
}
// Equal is used for comparing nodes by topology and id
func (n Node) Equal(other Node) bool {
return n.Topology == other.Topology && n.ID == other.ID
}
// After is used for sorting nodes by topology and id
func (n Node) After(other Node) bool {
return other.Topology < n.Topology || (other.Topology == n.Topology && other.ID < n.ID)
}
// WithLatests returns a fresh copy of n, with Metadata m merged in.
func (n Node) WithLatests(m map[string]string) Node {
result := n.Copy()
ts := mtime.Now()
for k, v := range m {
result.Latest = result.Latest.Set(k, ts, v)
}
return result
}
// WithLatest produces a new Node with k mapped to v in the Latest metadata.
func (n Node) WithLatest(k string, ts time.Time, v string) Node {
result := n.Copy()
result.Latest = result.Latest.Set(k, ts, v)
return result
}
// WithCounters returns a fresh copy of n, with Counters c merged in.
func (n Node) WithCounters(c map[string]int) Node {
result := n.Copy()
result.Counters = result.Counters.Merge(Counters{}.fromIntermediate(c))
return result
}
// WithSet returns a fresh copy of n, with set merged in at key.
func (n Node) WithSet(key string, set StringSet) Node {
result := n.Copy()
result.Sets = result.Sets.Add(key, set)
return result
}
// WithSets returns a fresh copy of n, with sets merged in.
func (n Node) WithSets(sets Sets) Node {
result := n.Copy()
result.Sets = result.Sets.Merge(sets)
return result
}
// WithMetric returns a fresh copy of n, with metric merged in at key.
func (n Node) WithMetric(key string, metric Metric) Node {
result := n.Copy()
result.Metrics[key] = n.Metrics[key].Merge(metric)
return result
}
// WithMetrics returns a fresh copy of n, with metrics merged in.
func (n Node) WithMetrics(metrics Metrics) Node {
result := n.Copy()
result.Metrics = result.Metrics.Merge(metrics)
return result
}
// WithAdjacent returns a fresh copy of n, with 'a' added to Adjacency
func (n Node) WithAdjacent(a ...string) Node {
result := n.Copy()
result.Adjacency = result.Adjacency.Add(a...)
return result
}
// WithEdge returns a fresh copy of n, with 'dst' added to Adjacency and md
// added to EdgeMetadata.
func (n Node) WithEdge(dst string, md EdgeMetadata) Node {
result := n.Copy()
result.Adjacency = result.Adjacency.Add(dst)
result.Edges = result.Edges.Add(dst, md)
return result
}
// WithControls returns a fresh copy of n, with cs added to Controls.
func (n Node) WithControls(cs ...string) Node {
result := n.Copy()
result.Controls = result.Controls.Add(cs...)
return result
}
// WithParents returns a fresh copy of n, with sets merged in.
func (n Node) WithParents(parents Sets) Node {
result := n.Copy()
result.Parents = result.Parents.Merge(parents)
return result
}
// Copy returns a value copy of the Node.
func (n Node) Copy() Node {
cp := MakeNode()
cp.ID = n.ID
cp.Topology = n.Topology
cp.Counters = n.Counters.Copy()
cp.Sets = n.Sets.Copy()
cp.Adjacency = n.Adjacency.Copy()
cp.Edges = n.Edges.Copy()
cp.Controls = n.Controls.Copy()
cp.Latest = n.Latest.Copy()
cp.Metrics = n.Metrics.Copy()
cp.Parents = n.Parents.Copy()
return cp
}
// Merge mergses the individual components of a node and returns a
// fresh node.
func (n Node) Merge(other Node) Node {
cp := n.Copy()
if cp.ID == "" {
cp.ID = other.ID
}
if cp.Topology == "" {
cp.Topology = other.Topology
}
cp.Counters = cp.Counters.Merge(other.Counters)
cp.Sets = cp.Sets.Merge(other.Sets)
cp.Adjacency = cp.Adjacency.Merge(other.Adjacency)
cp.Edges = cp.Edges.Merge(other.Edges)
cp.Controls = cp.Controls.Merge(other.Controls)
cp.Latest = cp.Latest.Merge(other.Latest)
cp.Metrics = cp.Metrics.Merge(other.Metrics)
cp.Parents = cp.Parents.Merge(other.Parents)
return cp
}

View File

@@ -2,10 +2,10 @@ package report_test
import (
"fmt"
"reflect"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
var benchmarkResult report.NodeSet
@@ -58,7 +58,7 @@ func TestMakeNodeSet(t *testing.T) {
func BenchmarkMakeNodeSet(b *testing.B) {
nodes := []report.Node{}
for i := 1000; i >= 0; i-- {
node := report.MakeNode().WithID(fmt.Sprint(i)).WithMetadata(map[string]string{
node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
})
@@ -129,14 +129,14 @@ func BenchmarkNodeSetAdd(b *testing.B) {
n := report.MakeNodeSet()
for i := 0; i < 600; i++ {
n = n.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithMetadata(map[string]string{
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
}),
)
}
node := report.MakeNode().WithID("401.5").WithMetadata(map[string]string{
node := report.MakeNode().WithID("401.5").WithLatests(map[string]string{
"a": "1",
"b": "2",
})
@@ -207,7 +207,7 @@ func BenchmarkNodeSetMerge(b *testing.B) {
n, other := report.MakeNodeSet(), report.MakeNodeSet()
for i := 0; i < 600; i++ {
n = n.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithMetadata(map[string]string{
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"a": "1",
"b": "2",
}),
@@ -216,7 +216,7 @@ func BenchmarkNodeSetMerge(b *testing.B) {
for i := 400; i < 1000; i++ {
other = other.Add(
report.MakeNode().WithID(fmt.Sprint(i)).WithMetadata(map[string]string{
report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{
"c": "1",
"d": "2",
}),

161
report/node_test.go Normal file
View File

@@ -0,0 +1,161 @@
package report_test
import (
"testing"
"time"
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
const (
PID = "pid"
Name = "name"
Domain = "domain"
)
func TestMergeNodes(t *testing.T) {
mtime.NowForce(time.Now())
defer mtime.NowReset()
for name, c := range map[string]struct {
a, b, want report.Nodes
}{
"Empty a": {
a: report.Nodes{},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Empty b": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Simple merge": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.2:12345": report.MakeNodeWith(map[string]string{
PID: "42",
Name: "curl",
Domain: "node-a.local",
}),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
":192.168.1.2:12345": report.MakeNodeWith(map[string]string{
PID: "42",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Merge conflict with rank difference": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{ // <-- same ID
Name: "curl",
Domain: "node-a.local",
}).WithLatest(PID, time.Now().Add(-1*time.Minute), "0"),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Merge conflict with no rank difference": {
a: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
b: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{ // <-- same ID
Name: "curl",
Domain: "node-a.local",
}).WithLatest(PID, time.Now().Add(-1*time.Minute), "0"),
},
want: report.Nodes{
":192.168.1.1:12345": report.MakeNodeWith(map[string]string{
PID: "23128",
Name: "curl",
Domain: "node-a.local",
}),
},
},
"Counters": {
a: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 13,
"b": 57,
"c": 89,
}),
},
b: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 78,
"b": 3,
"d": 47,
}),
},
want: report.Nodes{
"1": report.MakeNode().WithCounters(map[string]int{
"a": 91,
"b": 60,
"c": 89,
"d": 47,
}),
},
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {
t.Errorf("%s: %s", name, test.Diff(c.want, have))
}
}
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/weaveworks/scope/report"
)
func newu64(value uint64) *uint64 { return &value }
// Make sure we don't add a topology and miss it in the Topologies method.
func TestReportTopologies(t *testing.T) {
var (
@@ -28,19 +30,19 @@ func TestReportTopologies(t *testing.T) {
func TestNode(t *testing.T) {
{
node := report.MakeNode().WithMetadata(report.Metadata{
node := report.MakeNode().WithLatests(map[string]string{
"foo": "bar",
})
if node.Metadata["foo"] != "bar" {
t.Errorf("want foo, have %s", node.Metadata["foo"])
if v, _ := node.Latest.Lookup("foo"); v != "bar" {
t.Errorf("want foo, have %s", v)
}
}
{
node := report.MakeNode().WithCounters(report.Counters{
"foo": 1,
})
if node.Counters["foo"] != 1 {
t.Errorf("want foo, have %d", node.Counters["foo"])
node := report.MakeNode().WithCounters(
map[string]int{"foo": 1},
)
if value, _ := node.Counters.Lookup("foo"); value != 1 {
t.Errorf("want foo, have %d", value)
}
}
{
@@ -56,7 +58,7 @@ func TestNode(t *testing.T) {
if node.Adjacency[0] != "foo" {
t.Errorf("want foo, have %v", node.Adjacency)
}
if *node.Edges["foo"].EgressPacketCount != 13 {
if v, ok := node.Edges.Lookup("foo"); ok && *v.EgressPacketCount != 13 {
t.Errorf("want 13, have %v", node.Edges)
}
}

177
report/sets.go Normal file
View File

@@ -0,0 +1,177 @@
package report
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"reflect"
"sort"
"github.com/mndrix/ps"
)
// Sets is a string->set-of-strings map.
// It is immutable.
type Sets struct {
psMap ps.Map
}
// EmptySets is an empty Sets. Starts with this.
var EmptySets = Sets{ps.NewMap()}
// MakeSets returns EmptySets
func MakeSets() Sets {
return EmptySets
}
// Keys returns the keys for this set
func (s Sets) Keys() []string {
return s.psMap.Keys()
}
// Add the given value to the Sets.
func (s Sets) Add(key string, value StringSet) Sets {
if existingValue, ok := s.psMap.Lookup(key); ok {
value = value.Merge(existingValue.(StringSet))
}
return Sets{
psMap: s.psMap.Set(key, value),
}
}
// Lookup returns the sets stored under key.
func (s Sets) Lookup(key string) (StringSet, bool) {
if value, ok := s.psMap.Lookup(key); ok {
return value.(StringSet), true
}
return EmptyStringSet, false
}
// Size returns the number of elements
func (s Sets) Size() int {
return s.psMap.Size()
}
// Merge merges two sets maps into a fresh set, performing set-union merges as
// appropriate.
func (s Sets) Merge(other Sets) Sets {
var (
sSize = s.Size()
otherSize = other.Size()
result = s.psMap
iter = other.psMap
)
switch {
case sSize == 0:
return other
case otherSize == 0:
return s
case sSize < otherSize:
result, iter = iter, result
}
iter.ForEach(func(key string, value interface{}) {
set := value.(StringSet)
if existingSet, ok := result.Lookup(key); ok {
set = set.Merge(existingSet.(StringSet))
}
result = result.Set(key, set)
})
return Sets{result}
}
// Copy is a noop
func (s Sets) Copy() Sets {
return s
}
func (s Sets) String() string {
keys := []string{}
for _, k := range s.psMap.Keys() {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := s.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
fmt.Fprintf(buf, "}\n")
return buf.String()
}
// DeepEqual tests equality with other Sets
func (s Sets) DeepEqual(i interface{}) bool {
t, ok := i.(Sets)
if !ok {
return false
}
if s.psMap.Size() != t.psMap.Size() {
return false
}
equal := true
s.psMap.ForEach(func(k string, val interface{}) {
if otherValue, ok := t.psMap.Lookup(k); !ok {
equal = false
} else {
equal = equal && reflect.DeepEqual(val, otherValue)
}
})
return equal
}
func (s Sets) toIntermediate() map[string]StringSet {
intermediate := map[string]StringSet{}
s.psMap.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(StringSet)
})
return intermediate
}
func (s Sets) fromIntermediate(in map[string]StringSet) Sets {
out := ps.NewMap()
for k, v := range in {
out = out.Set(k, v)
}
return Sets{out}
}
// MarshalJSON implements json.Marshaller
func (s Sets) MarshalJSON() ([]byte, error) {
if s.psMap != nil {
return json.Marshal(s.toIntermediate())
}
return json.Marshal(nil)
}
// UnmarshalJSON implements json.Unmarshaler
func (s *Sets) UnmarshalJSON(input []byte) error {
in := map[string]StringSet{}
if err := json.Unmarshal(input, &in); err != nil {
return err
}
*s = Sets{}.fromIntermediate(in)
return nil
}
// GobEncode implements gob.Marshaller
func (s Sets) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(s.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (s *Sets) GobDecode(input []byte) error {
in := map[string]StringSet{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*s = Sets{}.fromIntermediate(in)
return nil
}

View File

@@ -0,0 +1,19 @@
package report
import (
"testing"
"github.com/weaveworks/scope/test/reflect"
)
func TestSets(t *testing.T) {
sets := EmptySets.Add("foo", MakeStringSet("bar"))
if v, _ := sets.Lookup("foo"); !reflect.DeepEqual(v, MakeStringSet("bar")) {
t.Fatal(v)
}
sets = sets.Merge(EmptySets.Add("foo", MakeStringSet("baz")))
if v, _ := sets.Lookup("foo"); !reflect.DeepEqual(v, MakeStringSet("bar", "baz")) {
t.Fatal(v)
}
}

43
report/sets_test.go Normal file
View File

@@ -0,0 +1,43 @@
package report_test
import (
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
func TestSetsMerge(t *testing.T) {
for _, testcase := range []struct {
a, b report.Sets
want map[string][]string
}{
{report.EmptySets, report.EmptySets, map[string][]string{}},
{
report.EmptySets,
report.EmptySets.Add("a", report.MakeStringSet("b")),
map[string][]string{"a": {"b"}},
},
{
report.EmptySets,
report.EmptySets.Add("a", report.MakeStringSet("b", "c")),
map[string][]string{"a": {"b", "c"}},
},
{
report.EmptySets.Add("a", report.MakeStringSet("1")).Add("b", report.MakeStringSet("2")),
report.EmptySets.Add("c", report.MakeStringSet("3")).Add("b", report.MakeStringSet("3")),
map[string][]string{"a": {"1"}, "b": {"2", "3"}, "c": {"3"}},
},
} {
haveSets := testcase.a.Merge(testcase.b)
have := map[string][]string{}
keys := haveSets.Keys()
for _, k := range keys {
have[k], _ = haveSets.Lookup(k)
}
if !reflect.DeepEqual(testcase.want, have) {
t.Errorf("%+v.Merge(%+v): want %+v, have %+v", testcase.a, testcase.b, testcase.want, have)
}
}
}

109
report/string_set.go Normal file
View File

@@ -0,0 +1,109 @@
package report
import (
"sort"
)
// StringSet is a sorted set of unique strings. Clients must use the Add
// method to add strings.
type StringSet []string
// EmptyStringSet is an empty string set.
var EmptyStringSet StringSet
// MakeStringSet makes a new StringSet with the given strings.
func MakeStringSet(strs ...string) StringSet {
if len(strs) <= 0 {
return nil
}
result := make([]string, len(strs))
copy(result, strs)
sort.Strings(result)
for i := 1; i < len(result); { // shuffle down any duplicates
if result[i-1] == result[i] {
result = append(result[:i-1], result[i:]...)
continue
}
i++
}
return StringSet(result)
}
// Contains returns true if the string set includes the given string
func (s StringSet) Contains(str string) bool {
i := sort.Search(len(s), func(i int) bool { return s[i] >= str })
return i < len(s) && s[i] == str
}
// 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 {
for _, str := range strs {
i := sort.Search(len(s), func(i int) bool { return s[i] >= str })
if i < len(s) && s[i] == str {
// The list already has the element.
continue
}
// It a new element, insert it in order.
s = append(s, "")
copy(s[i+1:], s[i:])
s[i] = str
}
return s
}
// Remove removes the strings from the StringSet. Remove is the only valid way
// to shrink a StringSet. Remove returns the StringSet to enable chaining.
func (s StringSet) Remove(strs ...string) StringSet {
for _, str := range strs {
i := sort.Search(len(s), func(i int) bool { return s[i] >= str })
if i >= len(s) || s[i] != str {
// The list does not have the element.
continue
}
// has the element, remove it.
s = append(s[:i], s[i+1:]...)
}
return s
}
// Merge combines the two StringSets and returns a new result.
func (s StringSet) Merge(other StringSet) StringSet {
switch {
case len(other) <= 0: // Optimise special case, to avoid allocating
return s // (note unit test DeepEquals breaks if we don't do this)
case len(s) <= 0:
return other
}
result := make(StringSet, len(s)+len(other))
for i, j, k := 0, 0, 0; ; k++ {
switch {
case i >= len(s):
copy(result[k:], other[j:])
return result[:k+len(other)-j]
case j >= len(other):
copy(result[k:], s[i:])
return result[:k+len(s)-i]
case s[i] < other[j]:
result[k] = s[i]
i++
case s[i] > other[j]:
result[k] = other[j]
j++
default: // equal
result[k] = s[i]
i++
j++
}
}
}
// Copy returns a value copy of the StringSet.
func (s StringSet) Copy() StringSet {
if s == nil {
return s
}
result := make(StringSet, len(s))
copy(result, s)
return result
}

26
report/string_set_test.go Normal file
View File

@@ -0,0 +1,26 @@
package report_test
import (
"testing"
"github.com/weaveworks/scope/report"
)
func TestStringSetContains(t *testing.T) {
for _, testcase := range []struct {
contents []string
target string
want bool
}{
{nil, "foo", false},
{[]string{}, "foo", false},
{[]string{"a"}, "foo", false},
{[]string{"a", "foo"}, "foo", true},
{[]string{"foo", "b"}, "foo", true},
} {
have := report.MakeStringSet(testcase.contents...).Contains(testcase.target)
if testcase.want != have {
t.Errorf("%+v.Contains(%q): want %v, have %v", testcase.contents, testcase.target, testcase.want, have)
}
}
}

View File

@@ -2,9 +2,7 @@ package report
import (
"fmt"
"sort"
"strings"
"time"
)
// Topology describes a specific view of a network. It consists of nodes and
@@ -29,11 +27,11 @@ func MakeTopology() Topology {
// The same topology is returned to enable chaining.
// This method is different from all the other similar methods
// in that it mutates the Topology, to solve issues of GC pressure.
func (t Topology) AddNode(nodeID string, nmd Node) Topology {
func (t Topology) AddNode(nodeID string, node Node) Topology {
if existing, ok := t.Nodes[nodeID]; ok {
nmd = nmd.Merge(existing)
node = node.Merge(existing)
}
t.Nodes[nodeID] = nmd
t.Nodes[nodeID] = node
return t
}
@@ -80,468 +78,13 @@ func (n Nodes) Merge(other Nodes) Nodes {
return cp
}
// Node describes a superset of the metadata that probes can collect about a
// given node in a given topology, along with the edges emanating from the
// node and metadata about those edges.
type Node struct {
ID string `json:"id,omitempty"`
Topology string `json:"topology,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Counters Counters `json:"counters,omitempty"`
Sets Sets `json:"sets,omitempty"`
Adjacency IDList `json:"adjacency"`
Edges EdgeMetadatas `json:"edges,omitempty"`
Controls NodeControls `json:"controls,omitempty"`
Latest LatestMap `json:"latest,omitempty"`
Metrics Metrics `json:"metrics,omitempty"`
Parents Sets `json:"parents,omitempty"`
}
// MakeNode creates a new Node with no initial metadata.
func MakeNode() Node {
return Node{
Metadata: Metadata{},
Counters: Counters{},
Adjacency: MakeIDList(),
Edges: EdgeMetadatas{},
Controls: MakeNodeControls(),
Latest: MakeLatestMap(),
Metrics: Metrics{},
Parents: Sets{},
}
}
// MakeNodeWith creates a new Node with the supplied map.
func MakeNodeWith(m map[string]string) Node {
return MakeNode().WithMetadata(m)
}
// WithID returns a fresh copy of n, with ID changed.
func (n Node) WithID(id string) Node {
result := n.Copy()
result.ID = id
return result
}
// WithTopology returns a fresh copy of n, with ID changed.
func (n Node) WithTopology(topology string) Node {
result := n.Copy()
result.Topology = topology
return result
}
// Before is used for sorting nodes by topology and id
func (n Node) Before(other Node) bool {
return n.Topology < other.Topology || (n.Topology == other.Topology && n.ID < other.ID)
}
// Equal is used for comparing nodes by topology and id
func (n Node) Equal(other Node) bool {
return n.Topology == other.Topology && n.ID == other.ID
}
// After is used for sorting nodes by topology and id
func (n Node) After(other Node) bool {
return other.Topology < n.Topology || (other.Topology == n.Topology && other.ID < n.ID)
}
// WithMetadata returns a fresh copy of n, with Metadata m merged in.
func (n Node) WithMetadata(m map[string]string) Node {
result := n.Copy()
result.Metadata = result.Metadata.Merge(m)
return result
}
// WithCounters returns a fresh copy of n, with Counters c merged in.
func (n Node) WithCounters(c map[string]int) Node {
result := n.Copy()
result.Counters = result.Counters.Merge(c)
return result
}
// WithSet returns a fresh copy of n, with set merged in at key.
func (n Node) WithSet(key string, set StringSet) Node {
result := n.Copy()
result.Sets = result.Sets.Merge(Sets{key: set})
return result
}
// WithSets returns a fresh copy of n, with sets merged in.
func (n Node) WithSets(sets Sets) Node {
result := n.Copy()
result.Sets = result.Sets.Merge(sets)
return result
}
// WithMetric returns a fresh copy of n, with metric merged in at key.
func (n Node) WithMetric(key string, metric Metric) Node {
result := n.Copy()
result.Metrics[key] = n.Metrics[key].Merge(metric)
return result
}
// WithMetrics returns a fresh copy of n, with metrics merged in.
func (n Node) WithMetrics(metrics Metrics) Node {
result := n.Copy()
result.Metrics = result.Metrics.Merge(metrics)
return result
}
// WithAdjacent returns a fresh copy of n, with 'a' added to Adjacency
func (n Node) WithAdjacent(a ...string) Node {
result := n.Copy()
result.Adjacency = result.Adjacency.Add(a...)
return result
}
// WithEdge returns a fresh copy of n, with 'dst' added to Adjacency and md
// added to EdgeMetadata.
func (n Node) WithEdge(dst string, md EdgeMetadata) Node {
result := n.Copy()
result.Adjacency = result.Adjacency.Add(dst)
result.Edges[dst] = md
return result
}
// WithControls returns a fresh copy of n, with cs added to Controls.
func (n Node) WithControls(cs ...string) Node {
result := n.Copy()
result.Controls = result.Controls.Add(cs...)
return result
}
// WithLatest produces a new Node with k mapped to v in the Latest metadata.
func (n Node) WithLatest(k string, ts time.Time, v string) Node {
result := n.Copy()
result.Latest = result.Latest.Set(k, ts, v)
return result
}
// WithParents returns a fresh copy of n, with sets merged in.
func (n Node) WithParents(parents Sets) Node {
result := n.Copy()
result.Parents = result.Parents.Merge(parents)
return result
}
// Copy returns a value copy of the Node.
func (n Node) Copy() Node {
cp := MakeNode()
cp.ID = n.ID
cp.Topology = n.Topology
cp.Metadata = n.Metadata.Copy()
cp.Counters = n.Counters.Copy()
cp.Sets = n.Sets.Copy()
cp.Adjacency = n.Adjacency.Copy()
cp.Edges = n.Edges.Copy()
cp.Controls = n.Controls.Copy()
cp.Latest = n.Latest.Copy()
cp.Metrics = n.Metrics.Copy()
cp.Parents = n.Parents.Copy()
return cp
}
// Merge mergses the individual components of a node and returns a
// fresh node.
func (n Node) Merge(other Node) Node {
cp := n.Copy()
if cp.ID == "" {
cp.ID = other.ID
}
if cp.Topology == "" {
cp.Topology = other.Topology
}
cp.Metadata = cp.Metadata.Merge(other.Metadata)
cp.Counters = cp.Counters.Merge(other.Counters)
cp.Sets = cp.Sets.Merge(other.Sets)
cp.Adjacency = cp.Adjacency.Merge(other.Adjacency)
cp.Edges = cp.Edges.Merge(other.Edges)
cp.Controls = cp.Controls.Merge(other.Controls)
cp.Latest = cp.Latest.Merge(other.Latest)
cp.Metrics = cp.Metrics.Merge(other.Metrics)
cp.Parents = cp.Parents.Merge(other.Parents)
return cp
}
// Metadata is a string->string map.
type Metadata map[string]string
// Merge merges two node metadata maps together. In case of conflict, the
// other (right-hand) side wins. Always reassign the result of merge to the
// destination. Merge does not modify the receiver.
func (m Metadata) Merge(other Metadata) Metadata {
result := m.Copy()
for k, v := range other {
result[k] = v // other takes precedence
}
return result
}
// Copy creates a deep copy of the Metadata.
func (m Metadata) Copy() Metadata {
result := Metadata{}
for k, v := range m {
result[k] = v
}
return result
}
// Counters is a string->int map.
type Counters map[string]int
// Merge merges two sets of counters into a fresh set of counters, summing
// values where appropriate.
func (c Counters) Merge(other Counters) Counters {
result := c.Copy()
for k, v := range other {
result[k] = result[k] + v
}
return result
}
// Copy creates a deep copy of the Counters.
func (c Counters) Copy() Counters {
result := Counters{}
for k, v := range c {
result[k] = v
}
return result
}
// Sets is a string->set-of-strings map.
type Sets map[string]StringSet
// Merge merges two sets maps into a fresh set, performing set-union merges as
// appropriate.
func (s Sets) Merge(other Sets) Sets {
result := s.Copy()
for k, v := range other {
if result == nil {
result = Sets{}
}
result[k] = result[k].Merge(v)
}
return result
}
// Copy returns a value copy of the sets map.
func (s Sets) Copy() Sets {
if s == nil {
return s
}
result := Sets{}
for k, v := range s {
result[k] = v.Copy()
}
return result
}
// StringSet is a sorted set of unique strings. Clients must use the Add
// method to add strings.
type StringSet []string
// MakeStringSet makes a new StringSet with the given strings.
func MakeStringSet(strs ...string) StringSet {
if len(strs) <= 0 {
return nil
}
result := make([]string, len(strs))
copy(result, strs)
sort.Strings(result)
for i := 1; i < len(result); { // shuffle down any duplicates
if result[i-1] == result[i] {
result = append(result[:i-1], result[i:]...)
continue
}
i++
}
return StringSet(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 {
for _, str := range strs {
i := sort.Search(len(s), func(i int) bool { return s[i] >= str })
if i < len(s) && s[i] == str {
// The list already has the element.
continue
}
// It a new element, insert it in order.
s = append(s, "")
copy(s[i+1:], s[i:])
s[i] = str
}
return s
}
// Remove removes the strings from the StringSet. Remove is the only valid way
// to shrink a StringSet. Remove returns the StringSet to enable chaining.
func (s StringSet) Remove(strs ...string) StringSet {
for _, str := range strs {
i := sort.Search(len(s), func(i int) bool { return s[i] >= str })
if i >= len(s) || s[i] != str {
// The list does not have the element.
continue
}
// has the element, remove it.
s = append(s[:i], s[i+1:]...)
}
return s
}
// Merge combines the two StringSets and returns a new result.
func (s StringSet) Merge(other StringSet) StringSet {
switch {
case len(other) <= 0: // Optimise special case, to avoid allocating
return s // (note unit test DeepEquals breaks if we don't do this)
case len(s) <= 0:
return other
}
result := make(StringSet, len(s)+len(other))
for i, j, k := 0, 0, 0; ; k++ {
switch {
case i >= len(s):
copy(result[k:], other[j:])
return result[:k+len(other)-j]
case j >= len(other):
copy(result[k:], s[i:])
return result[:k+len(s)-i]
case s[i] < other[j]:
result[k] = s[i]
i++
case s[i] > other[j]:
result[k] = other[j]
j++
default: // equal
result[k] = s[i]
i++
j++
}
}
}
// Copy returns a value copy of the StringSet.
func (s StringSet) Copy() StringSet {
if s == nil {
return s
}
result := make(StringSet, len(s))
copy(result, s)
return result
}
// EdgeMetadatas collect metadata about each edge in a topology. Keys are the
// remote node IDs, as in Adjacency.
type EdgeMetadatas map[string]EdgeMetadata
// Copy returns a value copy of the EdgeMetadatas.
func (e EdgeMetadatas) Copy() EdgeMetadatas {
cp := make(EdgeMetadatas, len(e))
for k, v := range e {
cp[k] = v.Copy()
}
return cp
}
// Merge merges the other object into this one, and returns the result object.
// The original is not modified.
func (e EdgeMetadatas) Merge(other EdgeMetadatas) EdgeMetadatas {
cp := e.Copy()
for k, v := range other {
cp[k] = cp[k].Merge(v)
}
return cp
}
// Flatten flattens all the EdgeMetadatas in this set and returns the result.
// The original is not modified.
func (e EdgeMetadatas) Flatten() EdgeMetadata {
result := EdgeMetadata{}
for _, v := range e {
result = result.Flatten(v)
}
return result
}
// EdgeMetadata describes a superset of the metadata that probes can possibly
// collect about a directed edge between two nodes in any topology.
type EdgeMetadata struct {
EgressPacketCount *uint64 `json:"egress_packet_count,omitempty"`
IngressPacketCount *uint64 `json:"ingress_packet_count,omitempty"`
EgressByteCount *uint64 `json:"egress_byte_count,omitempty"` // Transport layer
IngressByteCount *uint64 `json:"ingress_byte_count,omitempty"` // Transport layer
MaxConnCountTCP *uint64 `json:"max_conn_count_tcp,omitempty"`
}
// Copy returns a value copy of the EdgeMetadata.
func (e EdgeMetadata) Copy() EdgeMetadata {
return EdgeMetadata{
EgressPacketCount: cpu64ptr(e.EgressPacketCount),
IngressPacketCount: cpu64ptr(e.IngressPacketCount),
EgressByteCount: cpu64ptr(e.EgressByteCount),
IngressByteCount: cpu64ptr(e.IngressByteCount),
MaxConnCountTCP: cpu64ptr(e.MaxConnCountTCP),
}
}
// Reversed returns a value copy of the EdgeMetadata, with the direction reversed.
func (e EdgeMetadata) Reversed() EdgeMetadata {
return EdgeMetadata{
EgressPacketCount: cpu64ptr(e.IngressPacketCount),
IngressPacketCount: cpu64ptr(e.EgressPacketCount),
EgressByteCount: cpu64ptr(e.IngressByteCount),
IngressByteCount: cpu64ptr(e.EgressByteCount),
MaxConnCountTCP: cpu64ptr(e.MaxConnCountTCP),
}
}
func cpu64ptr(u *uint64) *uint64 {
if u == nil {
return nil
}
value := *u // oh man
return &value // this sucks
}
// Merge merges another EdgeMetadata into the receiver and returns the result.
// The receiver is not modified. The two edge metadatas should represent the
// same edge on different times.
func (e EdgeMetadata) Merge(other EdgeMetadata) EdgeMetadata {
cp := e.Copy()
cp.EgressPacketCount = merge(cp.EgressPacketCount, other.EgressPacketCount, sum)
cp.IngressPacketCount = merge(cp.IngressPacketCount, other.IngressPacketCount, sum)
cp.EgressByteCount = merge(cp.EgressByteCount, other.EgressByteCount, sum)
cp.IngressByteCount = merge(cp.IngressByteCount, other.IngressByteCount, sum)
cp.MaxConnCountTCP = merge(cp.MaxConnCountTCP, other.MaxConnCountTCP, max)
return cp
}
// Flatten sums two EdgeMetadatas and returns the result. The receiver is not
// modified. The two edge metadata windows should be the same duration; they
// should represent different edges at the same time.
func (e EdgeMetadata) Flatten(other EdgeMetadata) EdgeMetadata {
cp := e.Copy()
cp.EgressPacketCount = merge(cp.EgressPacketCount, other.EgressPacketCount, sum)
cp.IngressPacketCount = merge(cp.IngressPacketCount, other.IngressPacketCount, sum)
cp.EgressByteCount = merge(cp.EgressByteCount, other.EgressByteCount, sum)
cp.IngressByteCount = merge(cp.IngressByteCount, other.IngressByteCount, sum)
// Note that summing of two maximums doesn't always give us the true
// maximum. But it's a best effort.
cp.MaxConnCountTCP = merge(cp.MaxConnCountTCP, other.MaxConnCountTCP, sum)
return cp
}
// Validate checks the topology for various inconsistencies.
func (t Topology) Validate() error {
errs := []string{}
// Check all node metadatas are valid, and the keys are parseable, i.e.
// Check all nodes are valid, and the keys are parseable, i.e.
// contain a scope.
for nodeID, nmd := range t.Nodes {
if nmd.Metadata == nil {
errs = append(errs, fmt.Sprintf("node ID %q has nil metadata", nodeID))
}
if _, _, ok := ParseNodeID(nodeID); !ok {
errs = append(errs, fmt.Sprintf("invalid node ID %q", nodeID))
}
@@ -549,16 +92,16 @@ func (t Topology) Validate() error {
// Check all adjancency keys has entries in Node.
for _, dstNodeID := range nmd.Adjacency {
if _, ok := t.Nodes[dstNodeID]; !ok {
errs = append(errs, fmt.Sprintf("node metadata missing from adjacency %q -> %q", nodeID, dstNodeID))
errs = append(errs, fmt.Sprintf("node missing from adjacency %q -> %q", nodeID, dstNodeID))
}
}
// Check all the edge metadatas have entries in adjacencies
for dstNodeID := range nmd.Edges {
nmd.Edges.ForEach(func(dstNodeID string, _ EdgeMetadata) {
if _, ok := t.Nodes[dstNodeID]; !ok {
errs = append(errs, fmt.Sprintf("node %s metadatas missing for edge %q", dstNodeID, nodeID))
errs = append(errs, fmt.Sprintf("node %s missing for edge %q", dstNodeID, nodeID))
}
}
})
}
if len(errs) > 0 {
@@ -567,25 +110,3 @@ func (t Topology) Validate() error {
return nil
}
func merge(dst, src *uint64, op func(uint64, uint64) uint64) *uint64 {
if src == nil {
return dst
}
if dst == nil {
dst = new(uint64)
}
(*dst) = op(*dst, *src)
return dst
}
func sum(dst, src uint64) uint64 {
return dst + src
}
func max(dst, src uint64) uint64 {
if dst > src {
return dst
}
return src
}

View File

@@ -1,10 +1,10 @@
package report_test
import (
"reflect"
"testing"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/reflect"
)
func TestMakeStringSet(t *testing.T) {

View File

@@ -126,7 +126,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().WithMetadata(map[string]string{
Client54001NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ClientIP,
endpoint.Port: ClientPort54001,
process.PID: Client1PID,
@@ -137,7 +137,7 @@ var (
EgressByteCount: newu64(100),
}),
Client54002NodeID: report.MakeNode().WithMetadata(map[string]string{
Client54002NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ClientIP,
endpoint.Port: ClientPort54002,
process.PID: Client2PID,
@@ -148,7 +148,7 @@ var (
EgressByteCount: newu64(200),
}),
Server80NodeID: report.MakeNode().WithMetadata(map[string]string{
Server80NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ServerIP,
endpoint.Port: ServerPort,
process.PID: ServerPID,
@@ -156,7 +156,7 @@ var (
endpoint.Procspied: True,
}),
NonContainerNodeID: report.MakeNode().WithMetadata(map[string]string{
NonContainerNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ServerIP,
endpoint.Port: NonContainerClientPort,
process.PID: NonContainerPID,
@@ -165,7 +165,7 @@ var (
}).WithAdjacent(GoogleEndpointNodeID),
// Probe pseudo nodes
UnknownClient1NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownClient1NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient1IP,
endpoint.Port: UnknownClient1Port,
endpoint.Procspied: True,
@@ -174,7 +174,7 @@ var (
EgressByteCount: newu64(300),
}),
UnknownClient2NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownClient2NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient2IP,
endpoint.Port: UnknownClient2Port,
endpoint.Procspied: True,
@@ -183,7 +183,7 @@ var (
EgressByteCount: newu64(400),
}),
UnknownClient3NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownClient3NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient3IP,
endpoint.Port: UnknownClient3Port,
endpoint.Procspied: True,
@@ -192,7 +192,7 @@ var (
EgressByteCount: newu64(500),
}),
RandomClientNodeID: report.MakeNode().WithMetadata(map[string]string{
RandomClientNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: RandomClientIP,
endpoint.Port: RandomClientPort,
endpoint.Procspied: True,
@@ -201,7 +201,7 @@ var (
EgressByteCount: newu64(600),
}),
GoogleEndpointNodeID: report.MakeNode().WithMetadata(map[string]string{
GoogleEndpointNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: GoogleIP,
endpoint.Port: GooglePort,
endpoint.Procspied: True,
@@ -215,11 +215,11 @@ var (
process.Name: Client1Name,
docker.ContainerID: ClientContainerID,
report.HostNodeID: ClientHostNodeID,
}).WithID(ClientProcess1NodeID).WithTopology(report.Process).WithParents(report.Sets{
"host": report.MakeStringSet(ClientHostNodeID),
"container": report.MakeStringSet(ClientContainerNodeID),
"container_image": report.MakeStringSet(ClientContainerImageNodeID),
}).WithMetrics(report.Metrics{
}).WithID(ClientProcess1NodeID).WithTopology(report.Process).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ClientHostNodeID)).
Add("container", report.MakeStringSet(ClientContainerNodeID)).
Add("container_image", report.MakeStringSet(ClientContainerImageNodeID)),
).WithMetrics(report.Metrics{
process.CPUUsage: ClientProcess1CPUMetric,
process.MemoryUsage: ClientProcess1MemoryMetric,
}),
@@ -228,28 +228,28 @@ var (
process.Name: Client2Name,
docker.ContainerID: ClientContainerID,
report.HostNodeID: ClientHostNodeID,
}).WithID(ClientProcess2NodeID).WithTopology(report.Process).WithParents(report.Sets{
"host": report.MakeStringSet(ClientHostNodeID),
"container": report.MakeStringSet(ClientContainerNodeID),
"container_image": report.MakeStringSet(ClientContainerImageNodeID),
}),
}).WithID(ClientProcess2NodeID).WithTopology(report.Process).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ClientHostNodeID)).
Add("container", report.MakeStringSet(ClientContainerNodeID)).
Add("container_image", report.MakeStringSet(ClientContainerImageNodeID)),
),
ServerProcessNodeID: report.MakeNodeWith(map[string]string{
process.PID: ServerPID,
process.Name: ServerName,
docker.ContainerID: ServerContainerID,
report.HostNodeID: ServerHostNodeID,
}).WithID(ServerProcessNodeID).WithTopology(report.Process).WithParents(report.Sets{
"host": report.MakeStringSet(ServerHostNodeID),
"container": report.MakeStringSet(ServerContainerNodeID),
"container_image": report.MakeStringSet(ServerContainerImageNodeID),
}),
}).WithID(ServerProcessNodeID).WithTopology(report.Process).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)).
Add("container", report.MakeStringSet(ServerContainerNodeID)).
Add("container_image", report.MakeStringSet(ServerContainerImageNodeID)),
),
NonContainerProcessNodeID: report.MakeNodeWith(map[string]string{
process.PID: NonContainerPID,
process.Name: NonContainerName,
report.HostNodeID: ServerHostNodeID,
}).WithID(NonContainerProcessNodeID).WithTopology(report.Process).WithParents(report.Sets{
"host": report.MakeStringSet(ServerHostNodeID),
}),
}).WithID(NonContainerProcessNodeID).WithTopology(report.Process).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)),
),
},
},
Container: report.Topology{
@@ -262,18 +262,19 @@ var (
docker.LabelPrefix + "io.kubernetes.pod.name": ClientPodID,
kubernetes.PodID: ClientPodID,
kubernetes.Namespace: KubernetesNamespace,
}).WithLatest(docker.ContainerState, Now, docker.StateRunning).WithID(ClientContainerNodeID).WithTopology(report.Container).WithParents(report.Sets{
"host": report.MakeStringSet(ClientHostNodeID),
"container_image": report.MakeStringSet(ClientContainerImageNodeID),
"pod": report.MakeStringSet(ClientPodID),
}).WithMetrics(report.Metrics{
docker.ContainerState: docker.StateRunning,
}).WithID(ClientContainerNodeID).WithTopology(report.Container).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ClientHostNodeID)).
Add("container_image", report.MakeStringSet(ClientContainerImageNodeID)).
Add("pod", report.MakeStringSet(ClientPodID)),
).WithMetrics(report.Metrics{
docker.CPUTotalUsage: ClientContainerCPUMetric,
docker.MemoryUsage: ClientContainerMemoryMetric,
}),
ServerContainerNodeID: report.MakeNodeWith(map[string]string{
docker.ContainerID: ServerContainerID,
docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01",
docker.ContainerState: "running",
docker.ContainerState: docker.StateRunning,
docker.ImageID: ServerContainerImageID,
report.HostNodeID: ServerHostNodeID,
docker.LabelPrefix + render.AmazonECSContainerNameLabel: "server",
@@ -282,11 +283,11 @@ var (
docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID,
kubernetes.PodID: ServerPodID,
kubernetes.Namespace: KubernetesNamespace,
}).WithLatest(docker.ContainerState, Now, docker.StateRunning).WithID(ServerContainerNodeID).WithTopology(report.Container).WithParents(report.Sets{
"host": report.MakeStringSet(ServerHostNodeID),
"container_image": report.MakeStringSet(ServerContainerImageNodeID),
"pod": report.MakeStringSet(ServerPodID),
}).WithMetrics(report.Metrics{
}).WithID(ServerContainerNodeID).WithTopology(report.Container).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)).
Add("container_image", report.MakeStringSet(ServerContainerImageNodeID)).
Add("pod", report.MakeStringSet(ServerPodID)),
).WithMetrics(report.Metrics{
docker.CPUTotalUsage: ServerContainerCPUMetric,
docker.MemoryUsage: ServerContainerMemoryMetric,
}),
@@ -298,47 +299,47 @@ var (
docker.ImageID: ClientContainerImageID,
docker.ImageName: ClientContainerImageName,
report.HostNodeID: ClientHostNodeID,
}).WithParents(report.Sets{
"host": report.MakeStringSet(ClientHostNodeID),
}).WithID(ClientContainerImageNodeID).WithTopology(report.ContainerImage),
}).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ClientHostNodeID)),
).WithID(ClientContainerImageNodeID).WithTopology(report.ContainerImage),
ServerContainerImageNodeID: report.MakeNodeWith(map[string]string{
docker.ImageID: ServerContainerImageID,
docker.ImageName: ServerContainerImageName,
report.HostNodeID: ServerHostNodeID,
docker.LabelPrefix + "foo1": "bar1",
docker.LabelPrefix + "foo2": "bar2",
}).WithParents(report.Sets{
"host": report.MakeStringSet(ServerHostNodeID),
}).WithID(ServerContainerImageNodeID).WithTopology(report.ContainerImage),
}).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)),
).WithID(ServerContainerImageNodeID).WithTopology(report.ContainerImage),
},
},
Address: report.Topology{
Nodes: report.Nodes{
ClientAddressNodeID: report.MakeNode().WithMetadata(map[string]string{
ClientAddressNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ClientIP,
report.HostNodeID: ClientHostNodeID,
}).WithEdge(ServerAddressNodeID, report.EdgeMetadata{
MaxConnCountTCP: newu64(3),
}),
ServerAddressNodeID: report.MakeNode().WithMetadata(map[string]string{
ServerAddressNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: ServerIP,
report.HostNodeID: ServerHostNodeID,
}),
UnknownAddress1NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownAddress1NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient1IP,
}).WithAdjacent(ServerAddressNodeID),
UnknownAddress2NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownAddress2NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient2IP,
}).WithAdjacent(ServerAddressNodeID),
UnknownAddress3NodeID: report.MakeNode().WithMetadata(map[string]string{
UnknownAddress3NodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: UnknownClient3IP,
}).WithAdjacent(ServerAddressNodeID),
RandomAddressNodeID: report.MakeNode().WithMetadata(map[string]string{
RandomAddressNodeID: report.MakeNode().WithLatests(map[string]string{
endpoint.Addr: RandomClientIP,
}).WithAdjacent(ServerAddressNodeID),
},
@@ -349,9 +350,9 @@ var (
"host_name": ClientHostName,
"os": "Linux",
report.HostNodeID: ClientHostNodeID,
}).WithID(ClientHostNodeID).WithTopology(report.Host).WithSets(report.Sets{
host.LocalNetworks: report.MakeStringSet("10.10.10.0/24"),
}).WithMetrics(report.Metrics{
}).WithID(ClientHostNodeID).WithTopology(report.Host).WithSets(report.EmptySets.
Add(host.LocalNetworks, report.MakeStringSet("10.10.10.0/24")),
).WithMetrics(report.Metrics{
host.CPUUsage: ClientHostCPUMetric,
host.MemoryUsage: ClientHostMemoryMetric,
host.Load1: ClientHostLoad1Metric,
@@ -362,9 +363,9 @@ var (
"host_name": ServerHostName,
"os": "Linux",
report.HostNodeID: ServerHostNodeID,
}).WithID(ServerHostNodeID).WithTopology(report.Host).WithSets(report.Sets{
host.LocalNetworks: report.MakeStringSet("10.10.10.0/24"),
}).WithMetrics(report.Metrics{
}).WithID(ServerHostNodeID).WithTopology(report.Host).WithSets(report.EmptySets.
Add(host.LocalNetworks, report.MakeStringSet("10.10.10.0/24")),
).WithMetrics(report.Metrics{
host.CPUUsage: ServerHostCPUMetric,
host.MemoryUsage: ServerHostMemoryMetric,
host.Load1: ServerHostLoad1Metric,
@@ -381,20 +382,20 @@ var (
kubernetes.Namespace: KubernetesNamespace,
kubernetes.PodContainerIDs: ClientContainerID,
kubernetes.ServiceIDs: ServiceID,
}).WithID(ClientPodNodeID).WithTopology(report.Pod).WithParents(report.Sets{
"host": report.MakeStringSet(ClientHostNodeID),
"service": report.MakeStringSet(ServiceID),
}),
}).WithID(ClientPodNodeID).WithTopology(report.Pod).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ClientHostNodeID)).
Add("service", report.MakeStringSet(ServiceID)),
),
ServerPodNodeID: report.MakeNodeWith(map[string]string{
kubernetes.PodID: ServerPodID,
kubernetes.PodName: "pong-b",
kubernetes.Namespace: KubernetesNamespace,
kubernetes.PodContainerIDs: ServerContainerID,
kubernetes.ServiceIDs: ServiceID,
}).WithID(ServerPodNodeID).WithTopology(report.Pod).WithParents(report.Sets{
"host": report.MakeStringSet(ServerHostNodeID),
"service": report.MakeStringSet(ServiceID),
}),
}).WithID(ServerPodNodeID).WithTopology(report.Pod).WithParents(report.EmptySets.
Add("host", report.MakeStringSet(ServerHostNodeID)).
Add("service", report.MakeStringSet(ServiceID)),
),
},
},
Service: report.Topology{

View File

@@ -1,10 +1,11 @@
package test
import (
"reflect"
"runtime"
"testing"
"time"
"github.com/weaveworks/scope/test/reflect"
)
// Poll repeatedly evaluates condition until we either timeout, or it suceeds.

172
test/reflect/deepequal.go Normal file
View File

@@ -0,0 +1,172 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Deep equality test via reflection
package reflect
import (
"reflect"
)
// During deepValueEqual, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited comparisons are stored in a map indexed by visit.
type visit struct {
a1 uintptr
a2 uintptr
typ reflect.Type
}
// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
// if depth > 10 { panic("deepValueEqual") } // for debugging
hard := func(k reflect.Kind) bool {
switch k {
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
return true
}
return false
}
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
addr1 := v1.UnsafeAddr()
addr2 := v2.UnsafeAddr()
if addr1 > addr2 {
// Canonicalize order to reduce number of entries in visited.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are identical ...
if addr1 == addr2 {
return true
}
// ... or already seen
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}
// Remember for later.
visited[v] = true
}
if m := v1.MethodByName("DeepEqual"); m.IsValid() {
results := m.Call([]reflect.Value{v2})
if len(results) != 1 || results[0].Kind() != reflect.Bool {
panic("DeepEqual must return 1 bool")
}
return results[0].Bool()
}
switch v1.Kind() {
case reflect.Array:
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.Slice:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.Interface:
if v1.IsNil() || v2.IsNil() {
return v1.IsNil() == v2.IsNil()
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Ptr:
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !deepValueEqual(v1.Field(i), v2.Field(i), visited, depth+1) {
return false
}
}
return true
case reflect.Map:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for _, k := range v1.MapKeys() {
if !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
return false
}
}
return true
case reflect.Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
case reflect.Bool:
return v1.Bool() == v2.Bool()
case reflect.Float32, reflect.Float64:
return v1.Float() == v2.Float()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v1.Int() == v2.Int()
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v1.Uint() == v2.Uint()
case reflect.String:
return v1.String() == v2.String()
default:
// Normal equality suffices
if v1.CanInterface() && v2.CanInterface() {
return v1.Interface() == v1.Interface()
} else if v1.CanInterface() || v2.CanInterface() {
return false
}
return true
}
}
// DeepEqual tests for deep equality. It uses normal == equality where
// possible but will scan elements of arrays, slices, maps, and fields of
// structs. In maps, keys are compared with == but elements use deep
// equality. DeepEqual correctly handles recursive types. Functions are equal
// only if they are both nil.
// An empty slice is not equal to a nil slice.
func DeepEqual(a1, a2 interface{}) bool {
if a1 == nil || a2 == nil {
return a1 == a2
}
v1 := reflect.ValueOf(a1)
v2 := reflect.ValueOf(a2)
if v1.Type() != v2.Type() {
return false
}
return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}