diff --git a/render/detailed/links.go b/render/detailed/links.go index 9dd175d5f6..72a7e68716 100644 --- a/render/detailed/links.go +++ b/render/detailed/links.go @@ -67,12 +67,8 @@ var ( report.StatefulSet: podIDHashQueries, report.CronJob: podIDHashQueries, report.Service: { - // These recording rules must be defined in the prometheus config. - // NB: Pods need to be labeled and selected by their respective Service name, meaning: - // - The Service's `spec.selector` needs to select on `name` - // - The Service's `metadata.name` needs to be the same value as `spec.selector.name` - docker.CPUTotalUsage: `namespace_label_name:container_cpu_usage_seconds_total:sum_rate{label_name="{{label}}",namespace="{{namespace}}"}`, - docker.MemoryUsage: `namespace_label_name:container_memory_usage_bytes:sum{label_name="{{label}}",namespace="{{namespace}}"}`, + docker.CPUTotalUsage: `sum(rate(container_cpu_usage_seconds_total{image!="",namespace="{{namespace}}",_weave_pod_name="{{label}}",job="cadvisor",container_name!="POD"}[5m]))`, + docker.MemoryUsage: `sum(rate(container_memory_usage_bytes{image!="",namespace="{{namespace}}",_weave_pod_name="{{label}}",job="cadvisor",container_name!="POD"}[5m]))`, }, } ) @@ -102,7 +98,7 @@ func formatMetricQueries(filter string, ids []string) map[string]string { // RenderMetricURLs sets respective URLs for metrics in a node summary. Missing metrics // where we have a query for will be appended as an empty metric (no values or samples). -func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string) NodeSummary { +func RenderMetricURLs(summary NodeSummary, n report.Node, r report.Report, metricsGraphURL string) NodeSummary { if metricsGraphURL == "" { return summary } @@ -117,7 +113,7 @@ func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string maxprio = metric.Priority } - query := metricQuery(summary, n, metric.ID) + query := metricQuery(summary, n, r, metric.ID) ms = append(ms, metric) if query != "" { @@ -133,7 +129,7 @@ func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string continue } - query := metricQuery(summary, n, metadata.ID) + query := metricQuery(summary, n, r, metadata.ID) if query == "" { continue } @@ -155,7 +151,7 @@ func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string } // metricQuery returns the query for the given node and metric. -func metricQuery(summary NodeSummary, n report.Node, metricID string) string { +func metricQuery(summary NodeSummary, n report.Node, r report.Report, metricID string) string { queries := topologyQueries[n.Topology] if len(queries) == 0 { return "" @@ -163,12 +159,42 @@ func metricQuery(summary NodeSummary, n report.Node, metricID string) string { namespace, _ := n.Latest.Lookup(kubernetes.Namespace) name, _ := n.Latest.Lookup(docker.ContainerName) - r := strings.NewReplacer( - "{{label}}", summary.Label, + replacer := strings.NewReplacer( + "{{label}}", metricLabel(summary, n, r), "{{namespace}}", namespace, "{{containerName}}", name, ) - return r.Replace(queries[metricID]) + return replacer.Replace(queries[metricID]) +} + +func metricLabel(summary NodeSummary, n report.Node, r report.Report) string { + label := summary.Label + if n.Topology == report.Service { + deploymentTopology, ok := r.Topology(report.Deployment) + if ok { + deploymentNames := []string{} + for _, pod := range r.Pod.Nodes { + serviceParents, serviceOk := pod.Parents.Lookup(report.Service) + deploymentParents, deploymentOk := pod.Parents.Lookup(report.Deployment) + if serviceOk && deploymentOk && serviceParents.Contains(n.ID) { + for _, id := range deploymentParents { + deploymentNode, ok := deploymentTopology.Nodes[id] + if !ok { + continue + } + if name, ok := deploymentNode.Latest.Lookup(report.KubernetesName); ok { + deploymentNames = append(deploymentNames, name) + } + } + break + } + } + if len(deploymentNames) == 1 { + label = deploymentNames[0] + } + } + } + return label } // metricURL builds the URL by embedding it into the configured `metricsGraphURL`. diff --git a/render/detailed/links_test.go b/render/detailed/links_test.go index 9552e1da6e..593cf2f666 100644 --- a/render/detailed/links_test.go +++ b/render/detailed/links_test.go @@ -42,7 +42,7 @@ func nodeSummaryWithMetrics(label string, metrics []report.MetricRow) detailed.N func TestRenderMetricURLs_Disabled(t *testing.T) { s := nodeSummaryWithMetrics("foo", sampleMetrics) - result := detailed.RenderMetricURLs(s, samplePodNode, "") + result := detailed.RenderMetricURLs(s, samplePodNode, report.MakeReport(), "") assert.Empty(t, result.Metrics[0].URL) assert.Empty(t, result.Metrics[1].URL) @@ -50,7 +50,7 @@ func TestRenderMetricURLs_Disabled(t *testing.T) { func TestRenderMetricURLs_UnknownTopology(t *testing.T) { s := nodeSummaryWithMetrics("foo", sampleMetrics) - result := detailed.RenderMetricURLs(s, sampleUnknownNode, sampleMetricsGraphURL) + result := detailed.RenderMetricURLs(s, sampleUnknownNode, report.MakeReport(), sampleMetricsGraphURL) assert.Empty(t, result.Metrics[0].URL) assert.Empty(t, result.Metrics[1].URL) @@ -58,7 +58,7 @@ func TestRenderMetricURLs_UnknownTopology(t *testing.T) { func TestRenderMetricURLs_Pod(t *testing.T) { s := nodeSummaryWithMetrics("foo", sampleMetrics) - result := detailed.RenderMetricURLs(s, samplePodNode, sampleMetricsGraphURL) + result := detailed.RenderMetricURLs(s, samplePodNode, report.MakeReport(), sampleMetricsGraphURL) checkURL(t, result.Metrics[0].URL, sampleMetricsGraphURL, []string{"container_memory_usage_bytes", `pod_name=\"foo\"`, `namespace=\"noospace\"`}) @@ -68,7 +68,7 @@ func TestRenderMetricURLs_Pod(t *testing.T) { func TestRenderMetricURLs_Container(t *testing.T) { s := nodeSummaryWithMetrics("foo", sampleMetrics) - result := detailed.RenderMetricURLs(s, sampleContainerNode, sampleMetricsGraphURL) + result := detailed.RenderMetricURLs(s, sampleContainerNode, report.MakeReport(), sampleMetricsGraphURL) checkURL(t, result.Metrics[0].URL, sampleMetricsGraphURL, []string{"container_memory_usage_bytes", `name=\"cooname\"`}) @@ -77,7 +77,7 @@ func TestRenderMetricURLs_Container(t *testing.T) { } func TestRenderMetricURLs_EmptyMetrics(t *testing.T) { - result := detailed.RenderMetricURLs(detailed.NodeSummary{}, samplePodNode, sampleMetricsGraphURL) + result := detailed.RenderMetricURLs(detailed.NodeSummary{}, samplePodNode, report.MakeReport(), sampleMetricsGraphURL) m := result.Metrics[0] assert.Equal(t, docker.CPUTotalUsage, m.ID) @@ -94,7 +94,7 @@ func TestRenderMetricURLs_EmptyMetrics(t *testing.T) { func TestRenderMetricURLs_CombinedEmptyMetrics(t *testing.T) { s := nodeSummaryWithMetrics("foo", []report.MetricRow{{ID: docker.MemoryUsage, Priority: 1}}) - result := detailed.RenderMetricURLs(s, samplePodNode, sampleMetricsGraphURL) + result := detailed.RenderMetricURLs(s, samplePodNode, report.MakeReport(), sampleMetricsGraphURL) assert.NotEmpty(t, result.Metrics[0].URL) assert.False(t, result.Metrics[0].ValueEmpty) @@ -106,7 +106,7 @@ func TestRenderMetricURLs_CombinedEmptyMetrics(t *testing.T) { func TestRenderMetricURLs_QueryReplacement(t *testing.T) { s := nodeSummaryWithMetrics("foo", sampleMetrics) - result := detailed.RenderMetricURLs(s, samplePodNode, "http://example.test/?q=:query") + result := detailed.RenderMetricURLs(s, samplePodNode, report.MakeReport(), "http://example.test/?q=:query") checkURL(t, result.Metrics[0].URL, "http://example.test/?q=", []string{"container_memory_usage_bytes", `pod_name="foo"`, `namespace="noospace"`}) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 0382175342..960e102c58 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -154,7 +154,7 @@ func MakeNodeSummary(rc RenderContext, n report.Node) (NodeSummary, bool) { summary.Tables = topology.TableTemplates.Tables(n) } } - return RenderMetricURLs(summary, n, rc.MetricsGraphURL), true + return RenderMetricURLs(summary, n, rc.Report, rc.MetricsGraphURL), true } // SummarizeMetrics returns a copy of the NodeSummary where the metrics are