mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-11 11:47:32 +00:00
Merge branch 'master' of github.com:weaveworks/scope
This commit is contained in:
29
README.md
29
README.md
@@ -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
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
1
render/report.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
187
report/counters.go
Normal 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
|
||||
}
|
||||
124
report/counters_internal_test.go
Normal file
124
report/counters_internal_test.go
Normal 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
296
report/edge_metadatas.go
Normal 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
|
||||
}
|
||||
284
report/edge_metadatas_internal_test.go
Normal file
284
report/edge_metadatas_internal_test.go
Normal 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 }
|
||||
@@ -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 {
|
||||
|
||||
@@ -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...))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
179
report/latest_map_internal_test.go
Normal file
179
report/latest_map_internal_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
190
report/node.go
Normal 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
|
||||
}
|
||||
@@ -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
161
report/node_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
177
report/sets.go
Normal 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
|
||||
}
|
||||
19
report/sets_internal_test.go
Normal file
19
report/sets_internal_test.go
Normal 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
43
report/sets_test.go
Normal 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
109
report/string_set.go
Normal 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
26
report/string_set_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
172
test/reflect/deepequal.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user