mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
- Move render.Rpt to test.Report - Remove report/report_fixture_test.go; it was unused - Remove report fixture in app/ and make tests use test.Report - Move expected outputs into render/expected, so they can be reused
266 lines
7.9 KiB
Go
266 lines
7.9 KiB
Go
package render
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
// Renderer is something that can render a report to a set of RenderableNodes.
|
|
type Renderer interface {
|
|
Render(report.Report) RenderableNodes
|
|
AggregateMetadata(rpt report.Report, localID, remoteID string) AggregateMetadata
|
|
}
|
|
|
|
// Reduce renderer is a Renderer which merges together the output of several
|
|
// other renderers.
|
|
type Reduce []Renderer
|
|
|
|
// Map is a Renderer which produces a set of RenderableNodes from the set of
|
|
// RenderableNodes produced by another Renderer.
|
|
type Map struct {
|
|
MapFunc
|
|
Renderer
|
|
}
|
|
|
|
// LeafMap is a Renderer which produces a set of RenderableNodes from a report.Topology
|
|
// by using a map function and topology selector.
|
|
type LeafMap struct {
|
|
Selector report.TopologySelector
|
|
Mapper LeafMapFunc
|
|
Pseudo PseudoFunc
|
|
}
|
|
|
|
// FilterUnconnected is a Renderer which filters out unconnected nodes.
|
|
type FilterUnconnected struct {
|
|
Renderer
|
|
}
|
|
|
|
// MakeReduce is the only sane way to produce a Reduce Renderer.
|
|
func MakeReduce(renderers ...Renderer) Renderer {
|
|
return Reduce(renderers)
|
|
}
|
|
|
|
// Render produces a set of RenderableNodes given a Report.
|
|
func (r Reduce) Render(rpt report.Report) RenderableNodes {
|
|
result := RenderableNodes{}
|
|
for _, renderer := range r {
|
|
result.Merge(renderer.Render(rpt))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// AggregateMetadata produces an AggregateMetadata for a given edge.
|
|
func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) AggregateMetadata {
|
|
metadata := AggregateMetadata{}
|
|
for _, renderer := range r {
|
|
metadata.Merge(renderer.AggregateMetadata(rpt, localID, remoteID))
|
|
}
|
|
return metadata
|
|
}
|
|
|
|
// Render transforms a set of RenderableNodes produces by another Renderer.
|
|
// using a map function
|
|
func (m Map) Render(rpt report.Report) RenderableNodes {
|
|
output, _ := m.render(rpt)
|
|
return output
|
|
}
|
|
|
|
func (m Map) render(rpt report.Report) (RenderableNodes, map[string]string) {
|
|
input := m.Renderer.Render(rpt)
|
|
output := RenderableNodes{}
|
|
mapped := map[string]string{} // input node ID -> output node ID
|
|
adjacencies := map[string]report.IDList{} // output node ID -> input node Adjacencies
|
|
|
|
for _, inRenderable := range input {
|
|
outRenderable, ok := m.MapFunc(inRenderable)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
existing, ok := output[outRenderable.ID]
|
|
if ok {
|
|
outRenderable.Merge(existing)
|
|
}
|
|
|
|
output[outRenderable.ID] = outRenderable
|
|
mapped[inRenderable.ID] = outRenderable.ID
|
|
adjacencies[outRenderable.ID] = adjacencies[outRenderable.ID].Add(inRenderable.Adjacency...)
|
|
}
|
|
|
|
// Rewrite Adjacency for new node IDs.
|
|
// NB we don't do pseudo nodes here; we assume the input graph
|
|
// is properly-connected, and if the map func dropped a node,
|
|
// we drop links to it.
|
|
for outNodeID, inAdjacency := range adjacencies {
|
|
outAdjacency := report.MakeIDList()
|
|
for _, inAdjacent := range inAdjacency {
|
|
if outAdjacent, ok := mapped[inAdjacent]; ok {
|
|
outAdjacency = outAdjacency.Add(outAdjacent)
|
|
}
|
|
}
|
|
outNode := output[outNodeID]
|
|
outNode.Adjacency = outAdjacency
|
|
output[outNodeID] = outNode
|
|
}
|
|
|
|
return output, mapped
|
|
}
|
|
|
|
// AggregateMetadata gives the metadata of an edge from the perspective of the
|
|
// srcRenderableID. Since an edgeID can have multiple edges on the address
|
|
// level, it uses the supplied mapping function to translate address IDs to
|
|
// renderable node (mapped) IDs.
|
|
func (m Map) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) AggregateMetadata {
|
|
// First we need to map the ids in this layer into the ids in the underlying layer
|
|
_, mapped := m.render(rpt) // this maps from old -> new
|
|
inverted := map[string][]string{} // this maps from new -> old(s)
|
|
for k, v := range mapped {
|
|
existing := inverted[v]
|
|
existing = append(existing, k)
|
|
inverted[v] = existing
|
|
}
|
|
|
|
// Now work out a slice of edges this edge is constructed from
|
|
oldEdges := []struct{ src, dst string }{}
|
|
for _, oldSrcID := range inverted[srcRenderableID] {
|
|
for _, oldDstID := range inverted[dstRenderableID] {
|
|
oldEdges = append(oldEdges, struct{ src, dst string }{oldSrcID, oldDstID})
|
|
}
|
|
}
|
|
|
|
// Now recurse for each old edge
|
|
output := AggregateMetadata{}
|
|
for _, edge := range oldEdges {
|
|
metadata := m.Renderer.AggregateMetadata(rpt, edge.src, edge.dst)
|
|
output.Merge(metadata)
|
|
}
|
|
return output
|
|
}
|
|
|
|
// Render transforms a given Report into a set of RenderableNodes, which
|
|
// the UI will render collectively as a graph. Note that a RenderableNode will
|
|
// always be rendered with other nodes, and therefore contains limited detail.
|
|
//
|
|
// Nodes with the same mapped IDs will be merged.
|
|
func (m LeafMap) Render(rpt report.Report) RenderableNodes {
|
|
var (
|
|
t = m.Selector(rpt)
|
|
nodes = RenderableNodes{}
|
|
localNetworks = LocalNetworks(rpt)
|
|
)
|
|
|
|
// Build a set of RenderableNodes for all non-pseudo probes, and an
|
|
// addressID to nodeID lookup map. Multiple addressIDs can map to the same
|
|
// RenderableNodes.
|
|
source2mapped := map[string]string{} // source node ID -> mapped node ID
|
|
for nodeID, metadata := range t.NodeMetadatas {
|
|
mapped, ok := m.Mapper(metadata)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// mapped.ID needs not be unique over all addressIDs. If not, we merge with
|
|
// the existing data, on the assumption that the MapFunc returns the same
|
|
// data.
|
|
existing, ok := nodes[mapped.ID]
|
|
if ok {
|
|
mapped.Merge(existing)
|
|
}
|
|
|
|
origins := mapped.Origins
|
|
origins = origins.Add(nodeID)
|
|
origins = origins.Add(metadata[report.HostNodeID])
|
|
mapped.Origins = origins
|
|
|
|
nodes[mapped.ID] = mapped
|
|
source2mapped[nodeID] = mapped.ID
|
|
}
|
|
|
|
// Walk the graph and make connections.
|
|
for src, dsts := range t.Adjacency {
|
|
var (
|
|
srcNodeID, ok = report.ParseAdjacencyID(src)
|
|
srcRenderableID = source2mapped[srcNodeID] // must exist
|
|
srcRenderableNode = nodes[srcRenderableID] // must exist
|
|
)
|
|
if !ok {
|
|
log.Printf("bad adjacency ID %q", src)
|
|
continue
|
|
}
|
|
|
|
for _, dstNodeID := range dsts {
|
|
dstRenderableID, ok := source2mapped[dstNodeID]
|
|
if !ok {
|
|
pseudoNode, ok := m.Pseudo(srcNodeID, srcRenderableNode, dstNodeID, localNetworks)
|
|
if !ok {
|
|
continue
|
|
}
|
|
dstRenderableID = pseudoNode.ID
|
|
nodes[dstRenderableID] = pseudoNode
|
|
source2mapped[dstNodeID] = dstRenderableID
|
|
}
|
|
|
|
srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID)
|
|
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID)
|
|
edgeID := report.MakeEdgeID(srcNodeID, dstNodeID)
|
|
if md, ok := t.EdgeMetadatas[edgeID]; ok {
|
|
srcRenderableNode.AggregateMetadata.Merge(AggregateMetadataOf(md))
|
|
}
|
|
}
|
|
|
|
nodes[srcRenderableID] = srcRenderableNode
|
|
}
|
|
|
|
return nodes
|
|
}
|
|
|
|
// AggregateMetadata gives the metadata of an edge from the perspective of the
|
|
// srcRenderableID. Since an edgeID can have multiple edges on the address
|
|
// level, it uses the supplied mapping function to translate address IDs to
|
|
// renderable node (mapped) IDs.
|
|
func (m LeafMap) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) AggregateMetadata {
|
|
t := m.Selector(rpt)
|
|
metadata := report.EdgeMetadata{}
|
|
for edgeID, edgeMeta := range t.EdgeMetadatas {
|
|
src, dst, ok := report.ParseEdgeID(edgeID)
|
|
if !ok {
|
|
log.Printf("bad edge ID %q", edgeID)
|
|
continue
|
|
}
|
|
if src != report.TheInternet {
|
|
mapped, _ := m.Mapper(t.NodeMetadatas[src])
|
|
src = mapped.ID
|
|
}
|
|
if dst != report.TheInternet {
|
|
mapped, _ := m.Mapper(t.NodeMetadatas[dst])
|
|
dst = mapped.ID
|
|
}
|
|
if src == srcRenderableID && dst == dstRenderableID {
|
|
metadata.Flatten(edgeMeta)
|
|
}
|
|
}
|
|
return AggregateMetadataOf(metadata)
|
|
}
|
|
|
|
// Render produces a set of RenderableNodes given a Report
|
|
func (f FilterUnconnected) Render(rpt report.Report) RenderableNodes {
|
|
return OnlyConnected(f.Renderer.Render(rpt))
|
|
}
|
|
|
|
// OnlyConnected filters out unconnected RenderedNodes
|
|
func OnlyConnected(input RenderableNodes) RenderableNodes {
|
|
output := RenderableNodes{}
|
|
for id, node := range input {
|
|
if len(node.Adjacency) == 0 {
|
|
continue
|
|
}
|
|
|
|
output[id] = node
|
|
for _, id := range node.Adjacency {
|
|
output[id] = input[id]
|
|
}
|
|
}
|
|
return output
|
|
}
|