diff --git a/experimental/graphviz/handlers.go b/experimental/graphviz/handlers.go index 55369b694..307c55c03 100644 --- a/experimental/graphviz/handlers.go +++ b/experimental/graphviz/handlers.go @@ -10,10 +10,11 @@ import ( "os" "os/exec" + "github.com/weaveworks/scope/render/detailed" "github.com/weaveworks/scope/report" ) -func dot(w io.Writer, t report.Nodes) { +func dot(w io.Writer, t detailed.NodeSummaries) { fmt.Fprintf(w, "digraph G {\n") fmt.Fprintf(w, "\toutputorder=edgesfirst;\n") fmt.Fprintf(w, "\toverlap=scale;\n") diff --git a/experimental/graphviz/render.go b/experimental/graphviz/render.go index 8a3b85172..de452b89f 100644 --- a/experimental/graphviz/render.go +++ b/experimental/graphviz/render.go @@ -4,10 +4,11 @@ import ( "fmt" "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/render/detailed" "github.com/weaveworks/scope/report" ) -func renderTo(rpt report.Report, topology string) (report.Nodes, error) { +func renderTo(rpt report.Report, topology string) (detailed.NodeSummaries, error) { renderer, ok := map[string]render.Renderer{ "processes": render.FilterUnconnected(render.ProcessWithContainerNameRenderer), "processes-by-name": render.FilterUnconnected(render.ProcessNameRenderer), @@ -16,7 +17,7 @@ func renderTo(rpt report.Report, topology string) (report.Nodes, error) { "hosts": render.HostRenderer, }[topology] if !ok { - return report.Nodes{}, fmt.Errorf("unknown topology %v", topology) + return detailed.NodeSummaries{}, fmt.Errorf("unknown topology %v", topology) } - return renderer.Render(rpt), nil + return detailed.Summaries(renderer.Render(rpt)), nil } diff --git a/render/detailed/metrics.go b/render/detailed/metrics.go index 6f85751c0..5346dada2 100644 --- a/render/detailed/metrics.go +++ b/render/detailed/metrics.go @@ -141,7 +141,7 @@ func (m *MetricRow) CodecDecodeSelf(decoder *codec.Decoder) { } // NodeMetrics produces a table (to be consumed directly by the UI) based on -// an origin ID, which is (optimistically) a node ID in one of our topologies. +// an a report.Node, which is (hopefully) a node in one of our topologies. func NodeMetrics(n report.Node) []MetricRow { if _, ok := n.Counters.Lookup(n.Topology); ok { // This is a group of nodes, so no metrics! diff --git a/render/detailed/node.go b/render/detailed/node.go index 616849f46..8897f3ea9 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -181,11 +181,14 @@ var ( func children(n report.Node) []NodeSummaryGroup { summaries := map[string][]NodeSummary{} n.Children.ForEach(func(child report.Node) { - if child.ID != n.ID { - if summary, ok := MakeNodeSummary(child); ok { - summaries[child.Topology] = append(summaries[child.Topology], summary.SummarizeMetrics()) - } + if child.ID == n.ID { + return } + summary, ok := MakeNodeSummary(child) + if !ok { + return + } + summaries[child.Topology] = append(summaries[child.Topology], summary.SummarizeMetrics()) }) nodeSummaryGroups := []NodeSummaryGroup{} diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index b54dd6aa3..91d5adc0c 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -182,7 +182,7 @@ func TestMakeDetailedContainerNode(t *testing.T) { {ID: "docker_image_id", Value: fixture.ServerContainerImageID}, }, DockerLabels: []detailed.MetadataRow{ - {ID: "label_" + render.AmazonECSContainerNameLabel, Value: `server`}, + {ID: "label_" + detailed.AmazonECSContainerNameLabel, Value: `server`}, {ID: "label_foo1", Value: `bar1`}, {ID: "label_foo2", Value: `bar2`}, {ID: "label_io.kubernetes.pod.name", Value: "ping/pong-b"}, diff --git a/render/detailed/parents.go b/render/detailed/parents.go index 473cd56df..991820e09 100644 --- a/render/detailed/parents.go +++ b/render/detailed/parents.go @@ -6,7 +6,6 @@ import ( "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/probe/kubernetes" - "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" ) @@ -55,7 +54,7 @@ func Parents(r report.Report, n report.Node) (result []Parent) { } func containerParent(n report.Node) Parent { - label, _ := render.GetRenderableContainerName(n) + label := getRenderableContainerName(n) containerID, _ := n.Latest.Lookup(docker.ContainerID) return Parent{ ID: report.MakeContainerNodeID(containerID), diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 3511e1112..a61506dbb 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -20,6 +20,12 @@ const ( Heptagon = "heptagon" Hexagon = "hexagon" Cloud = "cloud" + + ImageNameNone = "" + + // Keys we use to render container names + AmazonECSContainerNameLabel = "com.amazonaws.ecs.container-name" + KubernetesContainerNameLabel = "io.kubernetes.container.name" ) // NodeSummaryGroup is a topology-typed group of children for a Node. @@ -88,9 +94,7 @@ func MakeNodeSummary(n report.Node) (NodeSummary, bool) { report.Host: hostNodeSummary, } if renderer, ok := renderers[n.Topology]; ok { - if summary, ok := baseNodeSummary(NodeSummary{}, n); ok { - return renderer(summary, n) - } + return renderer(baseNodeSummary(n), n) } return NodeSummary{}, false } @@ -129,15 +133,16 @@ func (n NodeSummary) Copy() NodeSummary { return result } -func baseNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { - base.ID = n.ID - base.Shape = Circle - base.Linkable = true - base.Metadata = NodeMetadata(n) - base.DockerLabels = NodeDockerLabels(n) - base.Metrics = NodeMetrics(n) - base.Adjacency = n.Adjacency.Copy() - return base, true +func baseNodeSummary(n report.Node) NodeSummary { + return NodeSummary{ + ID: n.ID, + Shape: Circle, + Linkable: true, + Metadata: NodeMetadata(n), + DockerLabels: NodeDockerLabels(n), + Metrics: NodeMetrics(n), + Adjacency: n.Adjacency.Copy(), + } } func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { @@ -158,7 +163,8 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { // try rendering it as an uncontained node if strings.HasPrefix(n.ID, render.MakePseudoNodeID(render.UncontainedID)) { base.Label = render.UncontainedMajor - base.Shape = Circle + base.Shape = Square + base.Stack = true base.LabelMinor = report.ExtractHostID(n) return base, true } @@ -202,7 +208,7 @@ func processNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { } func containerNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { - base.Label, _ = render.GetRenderableContainerName(n) + base.Label = getRenderableContainerName(n) if c, ok := n.Counters.Lookup(report.Container); ok { base.Stack = true @@ -235,7 +241,7 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bo base.Shape = Hexagon base.Stack = true - if base.Label == "" { + if base.Label == ImageNameNone { base.Label, _ = n.Latest.Lookup(docker.ImageID) if len(base.Label) > 12 { base.Label = base.Label[:12] @@ -356,3 +362,28 @@ func (n NodeSummaries) Copy() NodeSummaries { } return result } + +// getRenderableContainerName obtains a user-friendly container name, to render in the UI +func getRenderableContainerName(nmd report.Node) string { + for _, key := range []string{ + // Amazon's ecs-agent produces huge Docker container names, destructively + // derived from mangling Container Definition names in Task + // Definitions. + // + // However, the ecs-agent provides a label containing the original Container + // Definition name. + docker.LabelPrefix + AmazonECSContainerNameLabel, + // Kubernetes also mangles its Docker container names and provides a + // label with the original container name. However, note that this label + // is only provided by Kubernetes versions >= 1.2 (see + // https://github.com/kubernetes/kubernetes/pull/17234/ ) + docker.LabelPrefix + KubernetesContainerNameLabel, + docker.ContainerName, + docker.ContainerHostname, + } { + if label, ok := nmd.Latest.Lookup(key); ok { + return label + } + } + return "" +} diff --git a/render/detailed/summary_test.go b/render/detailed/summary_test.go new file mode 100644 index 000000000..db1c362a4 --- /dev/null +++ b/render/detailed/summary_test.go @@ -0,0 +1,186 @@ +package detailed_test + +import ( + "sort" + "testing" + "time" + + "github.com/weaveworks/scope/common/mtime" + "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/probe/host" + "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/render/detailed" + "github.com/weaveworks/scope/render/expected" + "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/test" + "github.com/weaveworks/scope/test/fixture" + "github.com/weaveworks/scope/test/reflect" +) + +func TestSummaries(t *testing.T) { + { + // Just a convenient source of some rendered nodes + have := detailed.Summaries(render.ProcessRenderer.Render(fixture.Report)) + // The ids of the processes rendered above + expectedIDs := []string{ + fixture.ClientProcess1NodeID, + fixture.ClientProcess2NodeID, + fixture.ServerProcessNodeID, + fixture.NonContainerProcessNodeID, + expected.UnknownPseudoNode1ID, + expected.UnknownPseudoNode2ID, + render.IncomingInternetID, + render.OutgoingInternetID, + } + sort.Strings(expectedIDs) + + // It should summarize each node + ids := []string{} + for id := range have { + ids = append(ids, id) + } + sort.Strings(ids) + if !reflect.DeepEqual(expectedIDs, ids) { + t.Fatalf("Expected Summaries to have summarized every node in the process renderer: %v, but got %v", expectedIDs, ids) + } + } + + // It should summarize nodes' metrics + { + t1, t2 := mtime.Now().Add(-1*time.Minute), mtime.Now() + metric := report.MakeMetric().Add(t1, 1).Add(t2, 2) + input := fixture.Report.Copy() + + input.Process.Nodes[fixture.ClientProcess1NodeID] = input.Process.Nodes[fixture.ClientProcess1NodeID].WithMetrics(report.Metrics{process.CPUUsage: metric}) + have := detailed.Summaries(render.ProcessRenderer.Render(input)) + + node, ok := have[fixture.ClientProcess1NodeID] + if !ok { + t.Fatalf("Expected output to have the node we added the metric to") + } + + var row detailed.MetricRow + ok = false + for _, metric := range node.Metrics { + if metric.ID == process.CPUUsage { + row = metric + ok = true + break + } + } + if !ok { + t.Fatalf("Expected node to have the metric we added") + } + + // Our summarized MetricRow + want := detailed.MetricRow{ + ID: process.CPUUsage, + Format: "percent", + Value: 2, + Metric: &report.Metric{ + Samples: nil, + Min: metric.Min, + Max: metric.Max, + First: metric.First, + Last: metric.Last, + }, + } + if !reflect.DeepEqual(want, row) { + t.Fatalf("Expected to have summarized the node's metrics: %s", test.Diff(want, row)) + } + } +} + +func TestMakeNodeSummary(t *testing.T) { + testcases := []struct { + name string + input report.Node + ok bool + want detailed.NodeSummary + }{ + { + name: "single process rendering", + input: expected.RenderedProcesses[fixture.ClientProcess1NodeID], + ok: true, + want: detailed.NodeSummary{ + ID: fixture.ClientProcess1NodeID, + Label: fixture.Client1Name, + LabelMinor: "client.hostname.com (10001)", + Rank: fixture.Client1Name, + Shape: "square", + Metadata: []detailed.MetadataRow{ + {ID: process.PID, Value: fixture.Client1PID, Prime: true, Datatype: "number"}, + }, + Metrics: []detailed.MetricRow{}, + Adjacency: report.MakeIDList(fixture.ServerProcessNodeID), + }, + }, + { + name: "single container rendering", + input: expected.RenderedContainers[fixture.ClientContainerNodeID], + ok: true, + want: detailed.NodeSummary{ + ID: fixture.ClientContainerNodeID, + Label: fixture.ClientContainerName, + LabelMinor: fixture.ClientHostName, + Rank: fixture.ClientContainerImageName, + Shape: "hexagon", + Linkable: true, + Metadata: []detailed.MetadataRow{ + {ID: docker.ContainerID, Value: fixture.ClientContainerID, Prime: true}, + }, + Metrics: []detailed.MetricRow{}, + Adjacency: report.MakeIDList(fixture.ServerContainerNodeID), + }, + }, + { + name: "single container image rendering", + input: expected.RenderedContainerImages[fixture.ClientContainerImageNodeID], + ok: true, + want: detailed.NodeSummary{ + ID: fixture.ClientContainerImageNodeID, + Label: fixture.ClientContainerImageName, + LabelMinor: "1 container", + Rank: fixture.ClientContainerImageName, + Shape: "hexagon", + Linkable: true, + Stack: true, + Metadata: []detailed.MetadataRow{ + {ID: docker.ImageID, Value: fixture.ClientContainerImageID, Prime: true}, + {ID: report.Container, Value: "1", Prime: true, Datatype: "number"}, + }, + Adjacency: report.MakeIDList(fixture.ServerContainerImageNodeID), + }, + }, + { + name: "single host rendering", + input: expected.RenderedHosts[fixture.ClientHostNodeID], + ok: true, + want: detailed.NodeSummary{ + ID: fixture.ClientHostNodeID, + Label: "client", + LabelMinor: "hostname.com", + Rank: "hostname.com", + Shape: "circle", + Linkable: true, + Metadata: []detailed.MetadataRow{ + {ID: host.HostName, Value: fixture.ClientHostName, Prime: false}, + }, + Metrics: []detailed.MetricRow{}, + Adjacency: report.MakeIDList(fixture.ServerHostNodeID), + }, + }, + } + for _, testcase := range testcases { + have, ok := detailed.MakeNodeSummary(testcase.input) + if ok != testcase.ok { + t.Errorf("%s: MakeNodeSummary failed: expected ok value to be: %v", testcase.name, testcase.ok) + continue + } + + if !reflect.DeepEqual(testcase.want, have) { + t.Errorf("%s: Node Summary did not match: %s", testcase.name, test.Diff(testcase.want, have)) + } + } +} diff --git a/render/expected/expected.go b/render/expected/expected.go index 0daf5daf3..fe7d60c0e 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -1,6 +1,9 @@ package expected import ( + "github.com/weaveworks/scope/probe/docker" + "github.com/weaveworks/scope/probe/host" + "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test/fixture" @@ -25,13 +28,41 @@ var ( } } pseudo = node(render.Pseudo) - endpoint = node("") // TODO: endpoints don't have a topology for some reason? Not being tagged? - process = node(report.Process) + endpoint = node(report.Endpoint) + processNode = node(report.Process) container = node(report.Container) containerImage = node(report.ContainerImage) pod = node(report.Pod) service = node(report.Service) - host = node(report.Host) + hostNode = node(report.Host) + + UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP) + UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP) + + unknownPseudoNode1 = func(adjacent ...string) report.Node { + return pseudo(UnknownPseudoNode1ID, adjacent...). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.UnknownClient1NodeID], + RenderedEndpoints[fixture.UnknownClient2NodeID], + )) + } + unknownPseudoNode2 = func(adjacent ...string) report.Node { + return pseudo(UnknownPseudoNode2ID, adjacent...). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.UnknownClient3NodeID], + )) + } + + theIncomingInternetNode = func(adjacent ...string) report.Node { + return pseudo(render.IncomingInternetID, adjacent...). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.RandomClientNodeID], + )) + } + + theOutgoingInternetNode = pseudo(render.OutgoingInternetID).WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.GoogleEndpointNodeID], + )) RenderedEndpoints = report.Nodes{ fixture.Client54001NodeID: endpoint(fixture.Client54001NodeID, fixture.Server80NodeID), @@ -43,81 +74,203 @@ var ( fixture.RandomClientNodeID: endpoint(fixture.RandomClientNodeID, fixture.Server80NodeID), fixture.NonContainerNodeID: endpoint(fixture.NonContainerNodeID, fixture.GoogleEndpointNodeID), fixture.GoogleEndpointNodeID: endpoint(fixture.GoogleEndpointNodeID), - }.Prune() + } RenderedProcesses = report.Nodes{ - fixture.ClientProcess1NodeID: process(fixture.ClientProcess1NodeID, fixture.ServerProcessNodeID), - fixture.ClientProcess2NodeID: process(fixture.ClientProcess2NodeID, fixture.ServerProcessNodeID), - fixture.ServerProcessNodeID: process(fixture.ServerProcessNodeID), - fixture.NonContainerProcessNodeID: process(fixture.NonContainerProcessNodeID, render.OutgoingInternetID), - unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerProcessNodeID), - unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerProcessNodeID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerProcessNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ClientProcess1NodeID: processNode(fixture.ClientProcess1NodeID, fixture.ServerProcessNodeID). + WithLatests(map[string]string{ + report.HostNodeID: fixture.ClientHostNodeID, + process.PID: fixture.Client1PID, + process.Name: fixture.Client1Name, + }). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + )), - unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP) - unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP) + fixture.ClientProcess2NodeID: processNode(fixture.ClientProcess2NodeID, fixture.ServerProcessNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54002NodeID], + )), + + fixture.ServerProcessNodeID: processNode(fixture.ServerProcessNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + )), + + fixture.NonContainerProcessNodeID: processNode(fixture.NonContainerProcessNodeID, render.OutgoingInternetID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.NonContainerNodeID], + )), + + UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerProcessNodeID), + UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerProcessNodeID), + + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerProcessNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } RenderedProcessNames = report.Nodes{ - fixture.Client1Name: process(fixture.Client1Name, fixture.ServerName), - fixture.ServerName: process(fixture.ServerName), - fixture.NonContainerName: process(fixture.NonContainerName, render.OutgoingInternetID), - unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerName), - unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerName), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerName), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.Client1Name: processNode(fixture.Client1Name, fixture.ServerName). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + )), - uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID) + fixture.ServerName: processNode(fixture.ServerName). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + )), + + fixture.NonContainerName: processNode(fixture.NonContainerName, render.OutgoingInternetID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.NonContainerNodeID], + RenderedProcesses[fixture.NonContainerProcessNodeID], + )), + + UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerName), + UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerName), + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerName), + render.OutgoingInternetID: theOutgoingInternetNode, + } + + uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID) + uncontainedServerNode = pseudo(uncontainedServerID, render.OutgoingInternetID).WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.NonContainerNodeID], + RenderedProcesses[fixture.NonContainerProcessNodeID], + )) RenderedContainers = report.Nodes{ - fixture.ClientContainerNodeID: container(fixture.ClientContainerNodeID, fixture.ServerContainerNodeID), - fixture.ServerContainerNodeID: container(fixture.ServerContainerNodeID), - uncontainedServerID: pseudo(uncontainedServerID, render.OutgoingInternetID), - // unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerContainerNodeID), - // unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerContainerNodeID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerContainerNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ClientContainerNodeID: container(fixture.ClientContainerNodeID, fixture.ServerContainerNodeID). + WithLatests(map[string]string{ + report.HostNodeID: fixture.ClientHostNodeID, + docker.ContainerID: fixture.ClientContainerID, + docker.ContainerName: fixture.ClientContainerName, + docker.ImageName: fixture.ClientContainerImageName, + }). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + )), + + fixture.ServerContainerNodeID: container(fixture.ServerContainerNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + )), + + uncontainedServerID: uncontainedServerNode, + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerContainerNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } RenderedContainerImages = report.Nodes{ - fixture.ClientContainerImageNodeID: containerImage(fixture.ClientContainerImageNodeID, fixture.ServerContainerImageNodeID), - fixture.ServerContainerImageNodeID: containerImage(fixture.ServerContainerImageNodeID), - uncontainedServerID: pseudo(uncontainedServerID, render.OutgoingInternetID), - // unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerContainerImageNodeID), - // unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerContainerImageNodeID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerContainerImageNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ClientContainerImageNodeID: containerImage(fixture.ClientContainerImageNodeID, fixture.ServerContainerImageNodeID). + WithLatests(map[string]string{ + report.HostNodeID: fixture.ClientHostNodeID, + docker.ImageID: fixture.ClientContainerImageID, + docker.ImageName: fixture.ClientContainerImageName, + }). + WithCounters(map[string]int{ + report.Container: 1, + }). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + RenderedContainers[fixture.ClientContainerNodeID], + )), - RenderedPods = report.Nodes{ - fixture.ClientPodNodeID: pod(fixture.ClientPodNodeID, fixture.ServerPodNodeID), - fixture.ServerPodNodeID: pod(fixture.ServerPodNodeID), - uncontainedServerID: pseudo(uncontainedServerID, render.OutgoingInternetID), - // unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerPodNodeID), - // unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerPodNodeID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerPodNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ServerContainerImageNodeID: containerImage(fixture.ServerContainerImageNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + RenderedContainers[fixture.ServerContainerNodeID], + )), + + uncontainedServerID: uncontainedServerNode, + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerContainerImageNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } RenderedHosts = report.Nodes{ - fixture.ClientHostNodeID: host(fixture.ClientHostNodeID, fixture.ServerHostNodeID), - fixture.ServerHostNodeID: host(fixture.ServerHostNodeID, render.OutgoingInternetID), - unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServerHostNodeID), - unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServerHostNodeID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServerHostNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ClientHostNodeID: hostNode(fixture.ClientHostNodeID, fixture.ServerHostNodeID). + WithLatests(map[string]string{ + host.HostName: fixture.ClientHostName, + }). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + RenderedContainers[fixture.ClientContainerNodeID], + RenderedContainerImages[fixture.ClientContainerImageNodeID], + //RenderedPods[fixture.ClientPodNodeID], #1142 + )), + + fixture.ServerHostNodeID: hostNode(fixture.ServerHostNodeID, render.OutgoingInternetID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + RenderedEndpoints[fixture.NonContainerNodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + RenderedProcesses[fixture.NonContainerProcessNodeID], + RenderedContainers[fixture.ServerContainerNodeID], + RenderedContainerImages[fixture.ServerContainerImageNodeID], + //RenderedPods[fixture.ServerPodNodeID], #1142 + )), + + UnknownPseudoNode1ID: unknownPseudoNode1(fixture.ServerHostNodeID), + UnknownPseudoNode2ID: unknownPseudoNode2(fixture.ServerHostNodeID), + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerHostNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } + + RenderedPods = report.Nodes{ + fixture.ClientPodNodeID: pod(fixture.ClientPodNodeID, fixture.ServerPodNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + RenderedContainers[fixture.ClientContainerNodeID], + )), + + fixture.ServerPodNodeID: pod(fixture.ServerPodNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Server80NodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + RenderedContainers[fixture.ServerContainerNodeID], + )), + + uncontainedServerID: uncontainedServerNode, + render.IncomingInternetID: theIncomingInternetNode(fixture.ServerPodNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } RenderedPodServices = report.Nodes{ - fixture.ServiceNodeID: service(fixture.ServiceNodeID, fixture.ServiceNodeID), - uncontainedServerID: pseudo(uncontainedServerID, render.OutgoingInternetID), - // unknownPseudoNode1ID: pseudo(unknownPseudoNode1ID, fixture.ServiceID), - // unknownPseudoNode2ID: pseudo(unknownPseudoNode2ID, fixture.ServiceID), - render.IncomingInternetID: pseudo(render.IncomingInternetID, fixture.ServiceNodeID), - render.OutgoingInternetID: pseudo(render.OutgoingInternetID), - }.Prune() + fixture.ServiceNodeID: service(fixture.ServiceNodeID, fixture.ServiceNodeID). + WithChildren(report.MakeNodeSet( + RenderedEndpoints[fixture.Client54001NodeID], + RenderedEndpoints[fixture.Client54002NodeID], + RenderedEndpoints[fixture.Server80NodeID], + RenderedProcesses[fixture.ClientProcess1NodeID], + RenderedProcesses[fixture.ClientProcess2NodeID], + RenderedProcesses[fixture.ServerProcessNodeID], + RenderedContainers[fixture.ClientContainerNodeID], + RenderedContainers[fixture.ServerContainerNodeID], + RenderedPods[fixture.ClientPodNodeID], + RenderedPods[fixture.ServerPodNodeID], + )), + + uncontainedServerID: uncontainedServerNode, + render.IncomingInternetID: theIncomingInternetNode(fixture.ServiceNodeID), + render.OutgoingInternetID: theOutgoingInternetNode, + } ) func newu64(value uint64) *uint64 { return &value } diff --git a/render/filters.go b/render/filters.go index e0c42e681..27ed285a4 100644 --- a/render/filters.go +++ b/render/filters.go @@ -3,6 +3,7 @@ package render import ( "strings" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/endpoint" "github.com/weaveworks/scope/probe/kubernetes" @@ -47,7 +48,7 @@ func ColorConnected(r Renderer) Renderer { output := input.Copy() for id := range connected { - output[id] = output[id].WithLatests(map[string]string{IsConnected: "true"}) + output[id] = output[id].WithLatest(IsConnected, mtime.Now(), "true") } return output }, diff --git a/render/mapping.go b/render/mapping.go index 26b536da7..ea1605bfe 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -28,11 +28,9 @@ const ( ipsKey = "ips" - AmazonECSContainerNameLabel = "com.amazonaws.ecs.container-name" - KubernetesContainerNameLabel = "io.kubernetes.container.name" - - // Topology for pseudo-nodes so we can differentiate them at the end + // Topology for pseudo-nodes and IPs so we can differentiate them at the end Pseudo = "pseudo" + IP = "IP" ) // MapFunc is anything which can take an arbitrary Node and @@ -59,31 +57,6 @@ func theInternetNode(m report.Node) report.Node { return NewDerivedPseudoNode(OutgoingInternetID, m) } -// GetRenderableContainerName obtains a user-friendly container name, to render in the UI -func GetRenderableContainerName(nmd report.Node) (string, bool) { - for _, key := range []string{ - // Amazon's ecs-agent produces huge Docker container names, destructively - // derived from mangling Container Definition names in Task - // Definitions. - // - // However, the ecs-agent provides a label containing the original Container - // Definition name. - docker.LabelPrefix + AmazonECSContainerNameLabel, - // Kubernetes also mangles its Docker container names and provides a - // label with the original container name. However, note that this label - // is only provided by Kubernetes versions >= 1.2 (see - // https://github.com/kubernetes/kubernetes/pull/17234/ ) - docker.LabelPrefix + KubernetesContainerNameLabel, - docker.ContainerName, - docker.ContainerHostname, - } { - if label, ok := nmd.Latest.Lookup(key); ok { - return label, true - } - } - return "", false -} - // MapEndpoint2IP maps endpoint nodes to their IP address, for joining // with container nodes. We drop endpoint nodes with pids, as they // will be joined to containers through the process topology, and we @@ -110,8 +83,8 @@ func MapEndpoint2IP(m report.Node, local report.Networks) report.Nodes { id := report.MakeScopedEndpointNodeID(scope, addr, "") idWithPort := report.MakeScopedEndpointNodeID(scope, addr, port) return report.Nodes{ - id: NewDerivedNode(id, m).WithTopology("IP"), - idWithPort: NewDerivedNode(idWithPort, m).WithTopology("IP"), + id: NewDerivedNode(id, m).WithTopology(IP), + idWithPort: NewDerivedNode(idWithPort, m).WithTopology(IP), } } @@ -135,7 +108,7 @@ func MapContainer2IP(m report.Node, _ report.Networks) report.Nodes { } id := report.MakeScopedEndpointNodeID(scope, addr, "") result[id] = NewDerivedNode(id, m). - WithTopology("IP"). + WithTopology(IP). WithLatests(map[string]string{docker.ContainerID: containerID}). WithCounters(map[string]int{ipsKey: 1}) @@ -150,7 +123,7 @@ func MapContainer2IP(m report.Node, _ report.Networks) report.Nodes { ip, port := mapping[1], mapping[2] id := report.MakeScopedEndpointNodeID("", ip, port) result[id] = NewDerivedNode(id, m). - WithTopology("IP"). + WithTopology(IP). WithLatests(map[string]string{docker.ContainerID: containerID}). WithCounters(map[string]int{ipsKey: 1}) @@ -226,43 +199,18 @@ func MapEndpoint2Process(n report.Node, local report.Networks) report.Nodes { return MapEndpoint2Pseudo(n, local) } - pid, ok := n.Latest.LookupEntry(process.PID) + pid, timestamp, ok := n.Latest.LookupEntry(process.PID) if !ok { return report.Nodes{} } - id := report.MakeProcessNodeID(report.ExtractHostID(n), pid.Value) + id := report.MakeProcessNodeID(report.ExtractHostID(n), pid) node := NewDerivedNode(id, n).WithTopology(report.Process) - node.Latest = node.Latest.Set(process.PID, pid.Timestamp, pid.Value) + node.Latest = node.Latest.Set(process.PID, timestamp, pid) node.Counters = node.Counters.Add(n.Topology, 1) return report.Nodes{id: node} } -// MapTo re-indexes a set of nodes by some key from Latest, changing the topology -// TODO(paulbellamy): not sure this works, or is general enough -func MapTo(topology, key string) func(report.Node, report.Networks) report.Nodes { - return func(n report.Node, local report.Networks) report.Nodes { - // Pass through pseudo nodes - if n.Topology == Pseudo { - fmt.Printf("[DEBUG] MapTo skipping %s %s", n.Topology, n.ID) - return report.Nodes{n.ID: n} - } - - // Throw away any nodes not having this key - entry, ok := n.Latest.LookupEntry(key) - if !ok { - fmt.Printf("[DEBUG] MapTo skipping %s %s: no key %s", n.Topology, n.ID, key) - return report.Nodes{} - } - - node := NewDerivedNode(entry.Value, n).WithTopology(topology) - node.Latest = node.Latest.Set(key, entry.Timestamp, entry.Value) - node.Counters = node.Counters.Add(n.Topology, 1) - fmt.Printf("[DEBUG] MapTo %s %s -> %s %s", n.Topology, n.ID, node.Topology, node.ID) - return report.Nodes{entry.Value: node} - } -} - // MapProcess2Container maps process Nodes to container // Nodes. // @@ -313,15 +261,15 @@ func MapProcess2Name(n report.Node, _ report.Networks) report.Nodes { return report.Nodes{n.ID: n} } - name, ok := n.Latest.LookupEntry(process.Name) + name, timestamp, ok := n.Latest.LookupEntry(process.Name) if !ok { return report.Nodes{} } - node := NewDerivedNode(name.Value, n).WithTopology(report.Process) - node.Latest = node.Latest.Set(process.Name, name.Timestamp, name.Value) + node := NewDerivedNode(name, n).WithTopology(report.Process) + node.Latest = node.Latest.Set(process.Name, timestamp, name) node.Counters = node.Counters.Add(n.Topology, 1) - return report.Nodes{name.Value: node} + return report.Nodes{name: node} } // MapContainer2ContainerImage maps container Nodes to container @@ -343,15 +291,15 @@ func MapContainer2ContainerImage(n report.Node, _ report.Networks) report.Nodes // 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.Latest.LookupEntry(docker.ImageID) + imageID, timestamp, ok := n.Latest.LookupEntry(docker.ImageID) if !ok { return report.Nodes{} } // Add container id key to the counters, which will later be counted to produce the minor label - id := report.MakeContainerImageNodeID(imageID.Value) + id := report.MakeContainerImageNodeID(imageID) result := NewDerivedNode(id, n).WithTopology(report.ContainerImage) - result.Latest = result.Latest.Set(docker.ImageID, imageID.Timestamp, imageID.Value) + result.Latest = result.Latest.Set(docker.ImageID, timestamp, imageID) result.Counters = result.Counters.Add(n.Topology, 1) return report.Nodes{id: result} } @@ -382,13 +330,13 @@ func MapX2Host(n report.Node, _ report.Networks) report.Nodes { if n.Topology == Pseudo { return report.Nodes{} } - hostNodeID, ok := n.Latest.LookupEntry(report.HostNodeID) + hostNodeID, timestamp, ok := n.Latest.LookupEntry(report.HostNodeID) if !ok { return report.Nodes{} } id := report.MakeHostNodeID(report.ExtractHostID(n)) result := NewDerivedNode(id, n).WithTopology(report.Host) - result.Latest = result.Latest.Set(report.HostNodeID, hostNodeID.Timestamp, hostNodeID.Value) + result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID) result.Counters = result.Counters.Add(n.Topology, 1) return report.Nodes{id: result} } @@ -397,14 +345,14 @@ func MapX2Host(n report.Node, _ report.Networks) report.Nodes { // host nodes or pseudo nodes. func MapEndpoint2Host(n report.Node, local report.Networks) report.Nodes { // Nodes without a hostid are treated as pseudo nodes - hostNodeID, ok := n.Latest.LookupEntry(report.HostNodeID) + hostNodeID, timestamp, ok := n.Latest.LookupEntry(report.HostNodeID) if !ok { return MapEndpoint2Pseudo(n, local) } id := report.MakeHostNodeID(report.ExtractHostID(n)) result := NewDerivedNode(id, n).WithTopology(report.Host) - result.Latest = result.Latest.Set(report.HostNodeID, hostNodeID.Timestamp, hostNodeID.Value) + result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID) result.Counters = result.Counters.Add(n.Topology, 1) return report.Nodes{id: result} } @@ -497,15 +445,15 @@ func MapContainer2Hostname(n report.Node, _ report.Networks) report.Nodes { // Otherwise, if some some reason the container doesn't have a hostname // (maybe slightly out of sync reports), just drop it - id, ok := n.Latest.LookupEntry(docker.ContainerHostname) + id, timestamp, ok := n.Latest.LookupEntry(docker.ContainerHostname) if !ok { return report.Nodes{} } - node := NewDerivedNode(id.Value, n) + node := NewDerivedNode(id, n) node.Latest = node.Latest. - Set(docker.ContainerHostname, id.Timestamp, id.Value). + Set(docker.ContainerHostname, timestamp, id). Delete(docker.ContainerName) // TODO(paulbellamy): total hack to render these by hostname instead. node.Counters = node.Counters.Add(n.Topology, 1) - return report.Nodes{id.Value: node} + return report.Nodes{id: node} } diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go index 6475b0403..47b42a6e8 100644 --- a/render/short_lived_connections_test.go +++ b/render/short_lived_connections_test.go @@ -2,7 +2,6 @@ package render_test import ( "fmt" - "reflect" "testing" "github.com/weaveworks/scope/probe/docker" @@ -10,7 +9,6 @@ import ( "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" - "github.com/weaveworks/scope/test" ) var ( @@ -68,21 +66,18 @@ var ( }, }, } - - want = (report.Nodes{ - render.IncomingInternetID: report.MakeNode(). - WithID(render.IncomingInternetID). - WithTopology(render.Pseudo). - WithAdjacent(report.MakeContainerNodeID(containerID)), - report.MakeContainerNodeID(containerID): report.MakeNode(). - WithID(report.MakeContainerNodeID(containerID)). - WithTopology(report.Container), - }) ) func TestShortLivedInternetNodeConnections(t *testing.T) { have := render.ContainerWithImageNameRenderer.Render(rpt).Prune() - if !reflect.DeepEqual(want, have) { - t.Error(test.Diff(want, have)) + + // Conntracked-only connections from the internet should be assigned to the internet pseudonode + internet, ok := have[render.IncomingInternetID] + if !ok { + t.Fatal("Expected output to have an incoming internet node") + } + + if !internet.Adjacency.Contains(report.MakeContainerNodeID(containerID)) { + t.Errorf("Expected internet node to have adjacency to %s, but only had %v", report.MakeContainerNodeID(containerID), internet.Adjacency) } } diff --git a/render/topologies.go b/render/topologies.go index 883a4c96c..b7656bda4 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -34,18 +34,18 @@ func (r processWithContainerNameRenderer) Render(rpt report.Report) report.Nodes outputs := report.Nodes{} for id, p := range processes { outputs[id] = p - containerIDEntry, ok := p.Latest.LookupEntry(docker.ContainerID) + containerID, timestamp, ok := p.Latest.LookupEntry(docker.ContainerID) if !ok { continue } - container, ok := containers[report.MakeContainerNodeID(containerIDEntry.Value)] + container, ok := containers[report.MakeContainerNodeID(containerID)] if !ok { continue } output := p.Copy() - output.Latest = output.Latest.Set(docker.ContainerID, containerIDEntry.Timestamp, containerIDEntry.Value) - if entry, ok := container.Latest.LookupEntry(docker.ContainerName); ok { - output.Latest = output.Latest.Set(docker.ContainerName, entry.Timestamp, entry.Value) + output.Latest = output.Latest.Set(docker.ContainerID, timestamp, containerID) + if containerName, timestamp, ok := container.Latest.LookupEntry(docker.ContainerName); ok { + output.Latest = output.Latest.Set(docker.ContainerName, timestamp, containerName) } outputs[id] = output } diff --git a/render/topologies_test.go b/render/topologies_test.go index d8c62c3ef..be51fb4ab 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -15,7 +15,7 @@ import ( func TestEndpointRenderer(t *testing.T) { have := render.EndpointRenderer.Render(fixture.Report).Prune() - want := expected.RenderedEndpoints + want := expected.RenderedEndpoints.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -23,7 +23,7 @@ func TestEndpointRenderer(t *testing.T) { func TestProcessRenderer(t *testing.T) { have := render.ProcessRenderer.Render(fixture.Report).Prune() - want := expected.RenderedProcesses + want := expected.RenderedProcesses.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -31,7 +31,7 @@ func TestProcessRenderer(t *testing.T) { func TestProcessNameRenderer(t *testing.T) { have := render.ProcessNameRenderer.Render(fixture.Report).Prune() - want := expected.RenderedProcessNames + want := expected.RenderedProcessNames.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -39,7 +39,7 @@ func TestProcessNameRenderer(t *testing.T) { func TestContainerRenderer(t *testing.T) { have := render.ContainerRenderer.Render(fixture.Report).Prune() - want := expected.RenderedContainers + want := expected.RenderedContainers.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -53,7 +53,7 @@ func TestContainerFilterRenderer(t *testing.T) { docker.LabelPrefix + "works.weave.role": "system", }) have := render.FilterSystem(render.ContainerRenderer).Render(input).Prune() - want := expected.RenderedContainers.Copy() + want := expected.RenderedContainers.Copy().Prune() delete(want, fixture.ClientContainerNodeID) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) @@ -84,7 +84,7 @@ func TestContainerWithHostIPsRenderer(t *testing.T) { func TestContainerImageRenderer(t *testing.T) { have := render.ContainerImageRenderer.Render(fixture.Report).Prune() - want := expected.RenderedContainerImages + want := expected.RenderedContainerImages.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -92,7 +92,7 @@ func TestContainerImageRenderer(t *testing.T) { func TestHostRenderer(t *testing.T) { have := render.HostRenderer.Render(fixture.Report).Prune() - want := expected.RenderedHosts + want := expected.RenderedHosts.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -100,7 +100,7 @@ func TestHostRenderer(t *testing.T) { func TestPodRenderer(t *testing.T) { have := render.PodRenderer.Render(fixture.Report).Prune() - want := expected.RenderedPods + want := expected.RenderedPods.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -119,7 +119,7 @@ func TestPodFilterRenderer(t *testing.T) { docker.LabelPrefix + "io.kubernetes.pod.name": "kube-system/foo", }) have := render.FilterSystem(render.PodRenderer).Render(input).Prune() - want := expected.RenderedPods.Copy() + want := expected.RenderedPods.Copy().Prune() delete(want, fixture.ClientPodNodeID) delete(want, fixture.ClientContainerNodeID) if !reflect.DeepEqual(want, have) { @@ -129,7 +129,7 @@ func TestPodFilterRenderer(t *testing.T) { func TestPodServiceRenderer(t *testing.T) { have := render.PodServiceRenderer.Render(fixture.Report).Prune() - want := expected.RenderedPodServices + want := expected.RenderedPodServices.Prune() if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } diff --git a/report/latest_map.go b/report/latest_map.go index ecdbd0d7e..117c2d7d2 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -86,26 +86,21 @@ func (m LatestMap) Merge(other 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 - } - return value.(LatestEntry).Value, true + v, _, ok := m.LookupEntry(key) + return v, ok } // LookupEntry returns the raw entry for the given key. -func (m LatestMap) LookupEntry(key string) (LatestEntry, bool) { +func (m LatestMap) LookupEntry(key string) (string, time.Time, bool) { if m.Map == nil { - return LatestEntry{}, false + return "", time.Time{}, false } value, ok := m.Map.Lookup(key) if !ok { - return LatestEntry{}, false + return "", time.Time{}, false } - return value.(LatestEntry), true + e := value.(LatestEntry) + return e.Value, e.Timestamp, true } // Set the value for the given key. diff --git a/report/latest_map_internal_test.go b/report/latest_map_internal_test.go index da8cbc527..e0c15a50e 100644 --- a/report/latest_map_internal_test.go +++ b/report/latest_map_internal_test.go @@ -33,11 +33,11 @@ func TestLatestMapLookupEntry(t *testing.T) { now := time.Now() entry := LatestEntry{Timestamp: now, Value: "Bar"} have := EmptyLatestMap.Set("foo", entry.Timestamp, entry.Value) - if got, ok := have.LookupEntry("foo"); !ok || got != entry { - t.Errorf("got: %#v != expected %#v", got, entry) + if got, timestamp, ok := have.LookupEntry("foo"); !ok || got != entry.Value || !timestamp.Equal(entry.Timestamp) { + t.Errorf("got: %#v %v != expected %#v", got, timestamp, entry) } - if got, ok := have.LookupEntry("not found"); ok { - t.Errorf("found unexpected entry for %q: %#v", "not found", got) + if got, timestamp, ok := have.LookupEntry("not found"); ok { + t.Errorf("found unexpected entry for %q: %#v %v", "not found", got, timestamp) } } diff --git a/report/node.go b/report/node.go index 6b2dceb90..bf731f63f 100644 --- a/report/node.go +++ b/report/node.go @@ -219,8 +219,13 @@ func (n Node) Merge(other Node) Node { // for rendering nodes and edges stripped away. Specifically, that means // cutting out parts of the Node. func (n Node) Prune() Node { + prunedChildren := MakeNodeSet() + n.Children.ForEach(func(child Node) { + prunedChildren = prunedChildren.Add(child.Prune()) + }) return MakeNode(). WithID(n.ID). WithTopology(n.Topology). - WithAdjacent(n.Adjacency.Copy()...) + WithAdjacent(n.Adjacency.Copy()...). + WithChildren(prunedChildren) } diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 818a63a32..4eacbc67a 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -8,7 +8,7 @@ import ( "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/probe/process" - "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/render/detailed" "github.com/weaveworks/scope/report" ) @@ -121,7 +121,7 @@ var ( // Node is arbitrary. We're free to put only precisely what we // care to test into the fixture. Just be sure to include the bits // that the mapping funcs extract :) - Client54001NodeID: report.MakeNode().WithID(Client54001NodeID).WithLatests(map[string]string{ + Client54001NodeID: report.MakeNode().WithID(Client54001NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: ClientIP, endpoint.Port: ClientPort54001, process.PID: Client1PID, @@ -132,7 +132,7 @@ var ( EgressByteCount: newu64(100), }), - Client54002NodeID: report.MakeNode().WithID(Client54002NodeID).WithLatests(map[string]string{ + Client54002NodeID: report.MakeNode().WithID(Client54002NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: ClientIP, endpoint.Port: ClientPort54002, process.PID: Client2PID, @@ -143,7 +143,7 @@ var ( EgressByteCount: newu64(200), }), - Server80NodeID: report.MakeNode().WithID(Server80NodeID).WithLatests(map[string]string{ + Server80NodeID: report.MakeNode().WithID(Server80NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: ServerIP, endpoint.Port: ServerPort, process.PID: ServerPID, @@ -151,7 +151,7 @@ var ( endpoint.Procspied: True, }), - NonContainerNodeID: report.MakeNode().WithID(NonContainerNodeID).WithLatests(map[string]string{ + NonContainerNodeID: report.MakeNode().WithID(NonContainerNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: ServerIP, endpoint.Port: NonContainerClientPort, process.PID: NonContainerPID, @@ -160,7 +160,7 @@ var ( }).WithAdjacent(GoogleEndpointNodeID), // Probe pseudo nodes - UnknownClient1NodeID: report.MakeNode().WithID(UnknownClient1NodeID).WithLatests(map[string]string{ + UnknownClient1NodeID: report.MakeNode().WithID(UnknownClient1NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: UnknownClient1IP, endpoint.Port: UnknownClient1Port, endpoint.Procspied: True, @@ -169,7 +169,7 @@ var ( EgressByteCount: newu64(300), }), - UnknownClient2NodeID: report.MakeNode().WithID(UnknownClient2NodeID).WithLatests(map[string]string{ + UnknownClient2NodeID: report.MakeNode().WithID(UnknownClient2NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: UnknownClient2IP, endpoint.Port: UnknownClient2Port, endpoint.Procspied: True, @@ -178,7 +178,7 @@ var ( EgressByteCount: newu64(400), }), - UnknownClient3NodeID: report.MakeNode().WithID(UnknownClient3NodeID).WithLatests(map[string]string{ + UnknownClient3NodeID: report.MakeNode().WithID(UnknownClient3NodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: UnknownClient3IP, endpoint.Port: UnknownClient3Port, endpoint.Procspied: True, @@ -187,7 +187,7 @@ var ( EgressByteCount: newu64(500), }), - RandomClientNodeID: report.MakeNode().WithID(RandomClientNodeID).WithLatests(map[string]string{ + RandomClientNodeID: report.MakeNode().WithID(RandomClientNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: RandomClientIP, endpoint.Port: RandomClientPort, endpoint.Procspied: True, @@ -196,7 +196,7 @@ var ( EgressByteCount: newu64(600), }), - GoogleEndpointNodeID: report.MakeNode().WithID(GoogleEndpointNodeID).WithLatests(map[string]string{ + GoogleEndpointNodeID: report.MakeNode().WithID(GoogleEndpointNodeID).WithTopology(report.Endpoint).WithLatests(map[string]string{ endpoint.Addr: GoogleIP, endpoint.Port: GooglePort, endpoint.Procspied: True, @@ -267,17 +267,17 @@ var ( docker.MemoryUsage: ClientContainerMemoryMetric, }), ServerContainerNodeID: report.MakeNodeWith(map[string]string{ - docker.ContainerID: ServerContainerID, - docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01", - docker.ContainerState: docker.StateRunning, - docker.ImageID: ServerContainerImageID, - report.HostNodeID: ServerHostNodeID, - docker.LabelPrefix + render.AmazonECSContainerNameLabel: "server", - docker.LabelPrefix + "foo1": "bar1", - docker.LabelPrefix + "foo2": "bar2", - docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID, - kubernetes.PodID: ServerPodID, - kubernetes.Namespace: KubernetesNamespace, + docker.ContainerID: ServerContainerID, + docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01", + docker.ContainerState: docker.StateRunning, + docker.ImageID: ServerContainerImageID, + report.HostNodeID: ServerHostNodeID, + docker.LabelPrefix + detailed.AmazonECSContainerNameLabel: "server", + docker.LabelPrefix + "foo1": "bar1", + docker.LabelPrefix + "foo2": "bar2", + docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID, + kubernetes.PodID: ServerPodID, + kubernetes.Namespace: KubernetesNamespace, }).WithID(ServerContainerNodeID).WithTopology(report.Container).WithParents(report.EmptySets. Add("host", report.MakeStringSet(ServerHostNodeID)). Add("container_image", report.MakeStringSet(ServerContainerImageNodeID)).