diff --git a/app/api_topologies.go b/app/api_topologies.go index a56a24385..f32cf1cd0 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -556,9 +556,9 @@ func captureReporter(rep Reporter, metricsGraphURL string, f reporterHandler) Ct } } -type rendererHandler func(context.Context, render.Renderer, render.Decorator, report.Report, string, http.ResponseWriter, *http.Request) +type rendererHandler func(context.Context, render.Renderer, render.Decorator, report.RenderContext, http.ResponseWriter, *http.Request) -func (r *Registry) captureRenderer(rep Reporter, metricGraphsURL string, f rendererHandler) CtxHandlerFunc { +func (r *Registry) captureRenderer(rep Reporter, metricsGraphURL string, f rendererHandler) CtxHandlerFunc { return func(ctx context.Context, w http.ResponseWriter, req *http.Request) { var ( topologyID = mux.Vars(req)["topology"] @@ -579,6 +579,6 @@ func (r *Registry) captureRenderer(rep Reporter, metricGraphsURL string, f rende respondWith(w, http.StatusInternalServerError, err) return } - f(ctx, renderer, decorator, rpt, metricGraphsURL, w, req) + f(ctx, renderer, decorator, report.RenderContext{Report: rpt, MetricsGraphURL: metricsGraphURL}, w, req) } } diff --git a/app/api_topology.go b/app/api_topology.go index a29f052d1..6d9710105 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -29,27 +29,27 @@ type APINode struct { } // Full topology. -func handleTopology(ctx context.Context, renderer render.Renderer, decorator render.Decorator, report report.Report, metricsGraphURL string, w http.ResponseWriter, r *http.Request) { +func handleTopology(ctx context.Context, renderer render.Renderer, decorator render.Decorator, rc report.RenderContext, w http.ResponseWriter, r *http.Request) { respondWith(w, http.StatusOK, APITopology{ - Nodes: detailed.Summaries(report, renderer.Render(report, decorator), metricsGraphURL), + Nodes: detailed.Summaries(rc, renderer.Render(rc.Report, decorator)), }) } // Individual nodes. -func handleNode(ctx context.Context, renderer render.Renderer, decorator render.Decorator, report report.Report, metricsGraphURL string, w http.ResponseWriter, r *http.Request) { +func handleNode(ctx context.Context, renderer render.Renderer, decorator render.Decorator, rc report.RenderContext, w http.ResponseWriter, r *http.Request) { var ( vars = mux.Vars(r) topologyID = vars["topology"] nodeID = vars["id"] preciousRenderer = render.PreciousNodeRenderer{PreciousNodeID: nodeID, Renderer: renderer} - rendered = preciousRenderer.Render(report, decorator) + rendered = preciousRenderer.Render(rc.Report, decorator) node, ok = rendered[nodeID] ) if !ok { http.NotFound(w, r) return } - respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, report, rendered, node, metricsGraphURL)}) + respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, rc, rendered, node)}) } // Websocket for the full topology. @@ -114,17 +114,20 @@ func handleWebsocket( // might be interested in implementing in the future. timestampDelta := time.Since(channelOpenedAt) reportTimestamp := startReportingAt.Add(timestampDelta) - report, err := rep.Report(ctx, reportTimestamp) + re, err := rep.Report(ctx, reportTimestamp) if err != nil { log.Errorf("Error generating report: %v", err) return } - renderer, decorator, err := topologyRegistry.RendererForTopology(topologyID, r.Form, report) + renderer, decorator, err := topologyRegistry.RendererForTopology(topologyID, r.Form, re) if err != nil { log.Errorf("Error generating report: %v", err) return } - newTopo := detailed.Summaries(report, renderer.Render(report, decorator), metricsGraphURL) + newTopo := detailed.Summaries( + report.RenderContext{Report: re, MetricsGraphURL: metricsGraphURL}, + renderer.Render(re, decorator), + ) diff := detailed.TopoDiff(previousTopo, newTopo) previousTopo = newTopo diff --git a/render/detailed/connections.go b/render/detailed/connections.go index 337e565ac..05afcd5f4 100644 --- a/render/detailed/connections.go +++ b/render/detailed/connections.go @@ -121,13 +121,13 @@ func internetAddr(node report.Node, ep report.Node) (string, bool) { return addr, true } -func (c *connectionCounters) rows(r report.Report, ns report.Nodes, includeLocal bool, metricsGraphURL string) []Connection { +func (c *connectionCounters) rows(r report.Report, ns report.Nodes, includeLocal bool) []Connection { output := []Connection{} for row, count := range c.counts { // Use MakeNodeSummary to render the id and label of this node // TODO(paulbellamy): Would be cleaner if we hade just a // MakeNodeID(ns[row.remoteNodeID]). As we don't need the whole summary. - summary, _ := MakeNodeSummary(r, ns[row.remoteNodeID], metricsGraphURL) + summary, _ := MakeNodeSummary(report.RenderContext{Report: r}, ns[row.remoteNodeID]) connection := Connection{ ID: fmt.Sprintf("%s-%s-%s-%s", row.remoteNodeID, row.remoteAddr, row.localAddr, row.port), NodeID: summary.ID, @@ -162,7 +162,7 @@ func (c *connectionCounters) rows(r report.Report, ns report.Nodes, includeLocal return output } -func incomingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes, metricsGraphURL string) ConnectionsSummary { +func incomingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes) ConnectionsSummary { localEndpointIDs, localEndpointIDCopies := endpointChildIDsAndCopyMapOf(n) counts := newConnectionCounters() @@ -188,11 +188,11 @@ func incomingConnectionsSummary(topologyID string, r report.Report, n report.Nod TopologyID: topologyID, Label: "Inbound", Columns: columnHeaders, - Connections: counts.rows(r, ns, isInternetNode(n), metricsGraphURL), + Connections: counts.rows(r, ns, isInternetNode(n)), } } -func outgoingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes, metricsGraphURL string) ConnectionsSummary { +func outgoingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes) ConnectionsSummary { localEndpoints := endpointChildrenOf(n) counts := newConnectionCounters() @@ -220,7 +220,7 @@ func outgoingConnectionsSummary(topologyID string, r report.Report, n report.Nod TopologyID: topologyID, Label: "Outbound", Columns: columnHeaders, - Connections: counts.rows(r, ns, isInternetNode(n), metricsGraphURL), + Connections: counts.rows(r, ns, isInternetNode(n)), } } diff --git a/render/detailed/links.go b/render/detailed/links.go index 957d80bc1..b2c2fba7c 100644 --- a/render/detailed/links.go +++ b/render/detailed/links.go @@ -99,8 +99,6 @@ func formatMetricQueries(filter string, ids []string) map[string]string { return queries } -var metricsGraphURL = "" - // 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 { @@ -119,10 +117,10 @@ func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string } query := metricQuery(summary, n, metric.ID) - ms = append(ms, metric) + if query != "" { - ms[len(ms)-1].URL = metricURL(query) + ms[len(ms)-1].URL = metricURL(query, metricsGraphURL) } found[metric.ID] = struct{}{} @@ -143,7 +141,7 @@ func RenderMetricURLs(summary NodeSummary, n report.Node, metricsGraphURL string ms = append(ms, report.MetricRow{ ID: metadata.ID, Label: metadata.Label, - URL: metricURL(query), + URL: metricURL(query, metricsGraphURL), Metric: &report.Metric{}, Priority: maxprio, ValueEmpty: true, @@ -170,7 +168,7 @@ func metricQuery(summary NodeSummary, n report.Node, metricID string) string { } // metricURL builds the URL by embedding it into the configured `metricsGraphURL`. -func metricURL(query string) string { +func metricURL(query, metricsGraphURL string) string { if strings.Contains(metricsGraphURL, urlQueryVarName) { return strings.Replace(metricsGraphURL, urlQueryVarName, url.QueryEscape(query), -1) } diff --git a/render/detailed/node.go b/render/detailed/node.go index 1481d99d8..59e6b0012 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -80,15 +80,15 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) { // MakeNode transforms a renderable node to a detailed node. It uses // aggregate metadata, plus the set of origin node IDs, to produce tables. -func MakeNode(topologyID string, r report.Report, ns report.Nodes, n report.Node, metricsGraphURL string) Node { - summary, _ := MakeNodeSummary(r, n, metricsGraphURL) +func MakeNode(topologyID string, rc report.RenderContext, ns report.Nodes, n report.Node) Node { + summary, _ := MakeNodeSummary(rc, n) return Node{ NodeSummary: summary, - Controls: controls(r, n), - Children: children(r, n, metricsGraphURL), + Controls: controls(rc.Report, n), + Children: children(rc, n), Connections: []ConnectionsSummary{ - incomingConnectionsSummary(topologyID, r, n, ns, metricsGraphURL), - outgoingConnectionsSummary(topologyID, r, n, ns, metricsGraphURL), + incomingConnectionsSummary(topologyID, rc.Report, n, ns), + outgoingConnectionsSummary(topologyID, rc.Report, n, ns), }, } } @@ -181,13 +181,13 @@ var nodeSummaryGroupSpecs = []struct { }, } -func children(r report.Report, n report.Node, metricsGraphURL string) []NodeSummaryGroup { +func children(rc report.RenderContext, n report.Node) []NodeSummaryGroup { summaries := map[string][]NodeSummary{} n.Children.ForEach(func(child report.Node) { if child.ID == n.ID { return } - summary, ok := MakeNodeSummary(r, child, metricsGraphURL) + summary, ok := MakeNodeSummary(rc, child) if !ok { return } @@ -216,7 +216,7 @@ func children(r report.Report, n report.Node, metricsGraphURL string) []NodeSumm if len(nodeSummaries) == 0 { continue } - topology, ok := r.Topology(topologyID) + topology, ok := rc.Topology(topologyID) if !ok { continue } diff --git a/render/detailed/summary.go b/render/detailed/summary.go index d302f6b38..58f0cec32 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -102,21 +102,22 @@ var primaryAPITopology = map[string]string{ } // MakeNodeSummary summarizes a node, if possible. -func MakeNodeSummary(r report.Report, n report.Node, metricsGraphURL string) (NodeSummary, bool) { +func MakeNodeSummary(rc report.RenderContext, n report.Node) (NodeSummary, bool) { + r := rc.Report if renderer, ok := renderers[n.Topology]; ok { // Skip (and don't fall through to fallback) if renderer maps to nil if renderer != nil { summary, b := renderer(baseNodeSummary(r, n), n) - return RenderMetricURLs(summary, n, metricsGraphURL), b + return RenderMetricURLs(summary, n, rc.MetricsGraphURL), b } - } else if _, ok := r.Topology(n.Topology); ok { + } else if _, ok := rc.Topology(n.Topology); ok { summary := baseNodeSummary(r, n) summary.Label = n.ID // This is unlikely to look very good, but is a reasonable fallback return summary, true } if strings.HasPrefix(n.Topology, "group:") { summary, b := groupNodeSummary(baseNodeSummary(r, n), r, n) - return RenderMetricURLs(summary, n, metricsGraphURL), b + return RenderMetricURLs(summary, n, rc.MetricsGraphURL), b } return NodeSummary{}, false } @@ -374,11 +375,11 @@ func (s nodeSummariesByID) Less(i, j int) bool { return s[i].ID < s[j].ID } type NodeSummaries map[string]NodeSummary // Summaries converts RenderableNodes into a set of NodeSummaries -func Summaries(r report.Report, rns report.Nodes, metricsGraphURL string) NodeSummaries { +func Summaries(rc report.RenderContext, rns report.Nodes) NodeSummaries { result := NodeSummaries{} for id, node := range rns { - if summary, ok := MakeNodeSummary(r, node, metricsGraphURL); ok { + if summary, ok := MakeNodeSummary(rc, node); ok { for i, m := range summary.Metrics { summary.Metrics[i] = m.Summary() } diff --git a/report/report.go b/report/report.go index 9cd6c6908..84eb88550 100644 --- a/report/report.go +++ b/report/report.go @@ -148,6 +148,12 @@ type Report struct { ID string `deepequal:"skip"` } +// RenderContext carries contextual data that is needed when rendering parts of the report. +type RenderContext struct { + Report + MetricsGraphURL string +} + // MakeReport makes a clean report, ready to Merge() other reports into. func MakeReport() Report { return Report{