package detailed_test import ( "context" "sort" "testing" "time" "github.com/weaveworks/common/mtime" "github.com/weaveworks/common/test" "github.com/weaveworks/scope/probe/docker" "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/fixture" "github.com/weaveworks/scope/test/reflect" ) func TestSummaries(t *testing.T) { { // Just a convenient source of some rendered nodes have := detailed.Summaries(context.Background(), detailed.RenderContext{Report: fixture.Report}, render.ProcessRenderer.Render(context.Background(), fixture.Report).Nodes) // The ids of the processes rendered above expectedIDs := []string{ fixture.ClientProcess1NodeID, fixture.ClientProcess2NodeID, fixture.ServerProcessNodeID, fixture.NonContainerProcessNodeID, 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([]report.Sample{{Timestamp: t1, Value: 1}, {Timestamp: t2, Value: 2}}) input := fixture.Report.Copy() processNode := input.Process.Nodes[fixture.ClientProcess1NodeID] processNode.Metrics = processNode.Metrics.Copy() processNode.Metrics[process.CPUUsage] = metric input.Process.Nodes[fixture.ClientProcess1NodeID] = processNode have := detailed.Summaries(context.Background(), detailed.RenderContext{Report: input}, render.ProcessRenderer.Render(context.Background(), input).Nodes) node, ok := have[fixture.ClientProcess1NodeID] if !ok { t.Fatalf("Expected output to have the node we added the metric to") } var row report.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 := report.MetricRow{ ID: process.CPUUsage, Label: "CPU", Format: "percent", Value: 2, Priority: 1, Metric: &report.Metric{ Samples: nil, Min: metric.Min, Max: metric.Max, }, } 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{ BasicNodeSummary: detailed.BasicNodeSummary{ ID: fixture.ClientProcess1NodeID, Label: fixture.Client1Name, LabelMinor: "client.hostname.com (10001)", Rank: fixture.Client1Name, Shape: "square", Tag: "", }, Metadata: []report.MetadataRow{ {ID: process.PID, Label: "PID", Value: fixture.Client1PID, Priority: 1, Datatype: report.Number}, }, Adjacency: report.MakeIDList(fixture.ServerProcessNodeID), }, }, { name: "single container rendering", input: expected.RenderedContainers[fixture.ClientContainerNodeID], ok: true, want: detailed.NodeSummary{ BasicNodeSummary: detailed.BasicNodeSummary{ ID: fixture.ClientContainerNodeID, Label: fixture.ClientContainerName, LabelMinor: fixture.ClientHostName, Rank: fixture.ClientContainerImageName, Shape: "hexagon", Tag: "", }, Metadata: []report.MetadataRow{ {ID: docker.ImageName, Label: "Image name", Value: fixture.ClientContainerImageName, Priority: 2}, {ID: docker.ContainerID, Label: "ID", Value: fixture.ClientContainerID, Priority: 11, Truncate: 12}, }, Adjacency: report.MakeIDList(fixture.ServerContainerNodeID), }, }, { name: "single container image rendering", input: expected.RenderedContainerImages[expected.ClientContainerImageNodeID], ok: true, want: detailed.NodeSummary{ BasicNodeSummary: detailed.BasicNodeSummary{ ID: expected.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, LabelMinor: "1 container", Rank: fixture.ClientContainerImageName, Shape: "hexagon", Tag: "", Stack: true, }, Metadata: []report.MetadataRow{ {ID: report.Container, Label: "# Containers", Value: "1", Priority: 2, Datatype: report.Number}, }, Adjacency: report.MakeIDList(expected.ServerContainerImageNodeID), }, }, { name: "single host rendering", input: expected.RenderedHosts[fixture.ClientHostNodeID], ok: true, want: detailed.NodeSummary{ BasicNodeSummary: detailed.BasicNodeSummary{ ID: fixture.ClientHostNodeID, Label: "client", LabelMinor: "hostname.com", Rank: "hostname.com", Shape: "circle", Tag: "", }, Metadata: []report.MetadataRow{ {ID: report.HostName, Label: "Hostname", Value: fixture.ClientHostName, Priority: 11}, }, Adjacency: report.MakeIDList(fixture.ServerHostNodeID), }, }, { name: "group node rendering", input: expected.RenderedProcessNames[fixture.ServerName], ok: true, want: detailed.NodeSummary{ BasicNodeSummary: detailed.BasicNodeSummary{ ID: "apache", Label: "apache", LabelMinor: "1 process", Rank: "apache", Shape: "square", Tag: "", Stack: true, }, }, }, } for _, testcase := range testcases { have, ok := detailed.MakeNodeSummary(detailed.RenderContext{Report: fixture.Report}, 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)) } } } func TestMakeNodeSummaryNoMetadata(t *testing.T) { processNameTopology := render.MakeGroupNodeTopology(report.Process, process.Name) for topology, id := range map[string]string{ render.Pseudo: render.MakePseudoNodeID("id"), report.Process: report.MakeProcessNodeID("ip-123-45-6-100", "1234"), report.Container: report.MakeContainerNodeID("0001accbecc2c95e650fe641926fb923b7cc307a71101a1200af3759227b6d7d"), report.ContainerImage: report.MakeContainerImageNodeID("0001accbecc2c95e650fe641926fb923b7cc307a71101a1200af3759227b6d7d"), report.Pod: report.MakePodNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.Service: report.MakeServiceNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.Deployment: report.MakeDeploymentNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.DaemonSet: report.MakeDaemonSetNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.StatefulSet: report.MakeStatefulSetNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.CronJob: report.MakeCronJobNodeID("005e2999-d429-11e7-8535-0a41257e78e8"), report.ECSTask: report.MakeECSTaskNodeID("arn:aws:ecs:us-east-1:012345678910:task/1dc5c17a-422b-4dc4-b493-371970c6c4d6"), report.ECSService: report.MakeECSServiceNodeID("cluster", "service"), report.SwarmService: report.MakeSwarmServiceNodeID("0001accbecc2c95e650fe641926fb923b7cc307a71101a1200af3759227b6d7d"), report.Host: report.MakeHostNodeID("ip-123-45-6-100"), report.Overlay: report.MakeOverlayNodeID("", "3e:ca:14:ca:12:5c"), processNameTopology: "/home/weave/scope", } { summary, b := detailed.MakeNodeSummary(detailed.RenderContext{}, report.MakeNode(id).WithTopology(topology)) switch { case !b: t.Errorf("Node Summary missing for topology %s, id %s", topology, id) case summary.Label == "": t.Errorf("Node Summary Label missing for topology %s, id %s", topology, id) case summary.Label == id && topology != processNameTopology: t.Errorf("Node Summary Label same as id (that's cheating!) for topology %s, id %s", topology, id) } } } func TestNodeMetadata(t *testing.T) { inputs := []struct { name string node report.Node want []report.MetadataRow }{ { name: "container", node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{ docker.ContainerID: fixture.ClientContainerID, docker.LabelPrefix + "label1": "label1value", docker.ContainerStateHuman: report.StateRunning, }).WithTopology(report.Container).WithSets(report.MakeSets(). Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")), ), want: []report.MetadataRow{ {ID: docker.ContainerStateHuman, Label: "State", Value: "running", Priority: 4}, {ID: docker.ContainerIPs, Label: "IPs", Value: "10.10.10.0/24, 10.10.10.1/24", Priority: 8}, {ID: docker.ContainerID, Label: "ID", Value: fixture.ClientContainerID, Priority: 11, Truncate: 12}, }, }, { name: "unknown topology", node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{ docker.ContainerID: fixture.ClientContainerID, }).WithTopology("foobar"), want: nil, }, } for _, input := range inputs { summary, _ := detailed.MakeNodeSummary(detailed.RenderContext{Report: fixture.Report}, input.node) have := summary.Metadata if !reflect.DeepEqual(input.want, have) { t.Errorf("%s: %s", input.name, test.Diff(input.want, have)) } } } func TestNodeMetrics(t *testing.T) { inputs := []struct { name string node report.Node want []report.MetricRow }{ { name: "process", node: fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], want: []report.MetricRow{ { ID: process.CPUUsage, Label: "CPU", Format: "percent", Group: "", Value: 0.01, Priority: 1, Metric: &fixture.ClientProcess1CPUMetric, }, { ID: process.MemoryUsage, Label: "Memory", Format: "filesize", Group: "", Value: 0.02, Priority: 2, Metric: &fixture.ClientProcess1MemoryMetric, }, }, }, { name: "container", node: fixture.Report.Container.Nodes[fixture.ClientContainerNodeID], want: []report.MetricRow{ { ID: docker.CPUTotalUsage, Label: "CPU", Format: "percent", Group: "", Value: 0.03, Priority: 1, Metric: &fixture.ClientContainerCPUMetric, }, { ID: docker.MemoryUsage, Label: "Memory", Format: "filesize", Group: "", Value: 0.04, Priority: 2, Metric: &fixture.ClientContainerMemoryMetric, }, }, }, { name: "host", node: fixture.Report.Host.Nodes[fixture.ClientHostNodeID], want: []report.MetricRow{ { ID: report.HostCPUUsage, Label: "CPU", Format: "percent", Group: "", Value: 0.07, Priority: 1, Metric: &fixture.ClientHostCPUMetric, }, { ID: report.HostMemoryUsage, Label: "Memory", Format: "filesize", Group: "", Value: 0.08, Priority: 2, Metric: &fixture.ClientHostMemoryMetric, }, { ID: report.Load1, Label: "Load (1m)", Group: "load", Value: 0.09, Priority: 11, Metric: &fixture.ClientHostLoad1Metric, }, }, }, { name: "unknown topology", node: report.MakeNode(fixture.ClientContainerNodeID).WithTopology("foobar"), want: nil, }, } for _, input := range inputs { summary, _ := detailed.MakeNodeSummary(detailed.RenderContext{Report: fixture.Report}, input.node) have := summary.Metrics if !reflect.DeepEqual(input.want, have) { t.Errorf("%s: %s", input.name, test.Diff(input.want, have)) } } } func TestMetricRowSummary(t *testing.T) { var ( now = time.Now() metric = report.MakeSingletonMetric(now, 1.234) row = report.MetricRow{ ID: "id", Format: "format", Group: "group", Value: 1.234, Priority: 1, Metric: &metric, } summary = row.Summary() ) // summary should not have any samples if summary.Metric.Len() != 0 { t.Errorf("Expected summary to have no samples, but had %d", summary.Metric.Len()) } // original metric should still have its samples if metric.Len() != 1 { t.Errorf("Expected original metric to still have it's samples, but had %d", metric.Len()) } // summary should have all the same fields (minus the metric) summary.Metric = nil row.Metric = nil if !reflect.DeepEqual(summary, row) { t.Errorf("Expected summary to have same fields as original: %s", test.Diff(summary, row)) } } func TestNodeTables(t *testing.T) { inputs := []struct { name string rpt report.Report node report.Node want []report.Table }{ { name: "container", rpt: report.Report{ Container: report.MakeTopology(). WithTableTemplates(docker.ContainerTableTemplates), }, node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{ docker.ContainerID: fixture.ClientContainerID, docker.LabelPrefix + "label1": "label1value", docker.ContainerState: report.StateRunning, }).WithTopology(report.Container).WithSets(report.MakeSets(). Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")), ), want: []report.Table{ { ID: docker.EnvPrefix, Type: report.PropertyListType, Label: "Environment variables", Rows: []report.Row{}, }, { ID: docker.LabelPrefix, Type: report.PropertyListType, Label: "Docker labels", Rows: []report.Row{ { ID: "label_label1", Entries: map[string]string{ "label": "label1", "value": "label1value", }, }, }, }, { ID: docker.ImageTableID, Type: report.PropertyListType, Label: "Image", Rows: []report.Row{}, }, }, }, { name: "unknown topology", rpt: report.MakeReport(), node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{ docker.ContainerID: fixture.ClientContainerID, }).WithTopology("foobar"), want: nil, }, } for _, input := range inputs { summary, _ := detailed.MakeNodeSummary(detailed.RenderContext{Report: input.rpt}, input.node) have := summary.Tables if !reflect.DeepEqual(input.want, have) { t.Errorf("%s: %s", input.name, test.Diff(input.want, have)) } } }