Files
weave-scope/render/render.go
Matthias Radestock aaf4e54df2 pass render filters and maps by value rather than reference
They are small and don't carry mutable state, so there is no point
passing them by reference.
2017-11-18 12:10:00 +00:00

236 lines
6.2 KiB
Go

package render
import (
"github.com/weaveworks/scope/report"
)
// MapFunc is anything which can take an arbitrary Node and
// return a set of other Nodes.
//
// If the output is empty, the node shall be omitted from the rendered topology.
type MapFunc func(report.Node, report.Networks) report.Nodes
// Renderer is something that can render a report to a set of Nodes.
type Renderer interface {
Render(report.Report, Decorator) Nodes
}
// Nodes is the result of Rendering
type Nodes struct {
report.Nodes
Filtered int
}
// Merge merges the results of Rendering
func (r Nodes) Merge(o Nodes) Nodes {
return Nodes{
Nodes: r.Nodes.Merge(o.Nodes),
Filtered: r.Filtered + o.Filtered,
}
}
// Reduce renderer is a Renderer which merges together the output of several
// other renderers.
type Reduce []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 Nodes given a Report.
func (r Reduce) Render(rpt report.Report, dct Decorator) Nodes {
l := len(r)
switch l {
case 0:
return Nodes{}
}
c := make(chan Nodes, l)
for _, renderer := range r {
renderer := renderer // Pike!!
go func() {
c <- renderer.Render(rpt, dct)
}()
}
for ; l > 1; l-- {
left, right := <-c, <-c
go func() {
c <- left.Merge(right)
}()
}
return <-c
}
// Map is a Renderer which produces a set of Nodes from the set of
// Nodes produced by another Renderer.
type Map struct {
MapFunc
Renderer
}
// MakeMap makes a new Map
func MakeMap(f MapFunc, r Renderer) Renderer {
return Map{f, r}
}
// Render transforms a set of Nodes produces by another Renderer.
// using a map function
func (m Map) Render(rpt report.Report, dct Decorator) Nodes {
var (
input = m.Renderer.Render(rpt, dct)
output = report.Nodes{}
mapped = map[string]report.IDList{} // input node ID -> output node IDs
adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies
localNetworks = LocalNetworks(rpt)
)
// Rewrite all the nodes according to the map function
for _, inRenderable := range input.Nodes {
for _, outRenderable := range m.MapFunc(inRenderable, localNetworks) {
if existing, ok := output[outRenderable.ID]; ok {
outRenderable = outRenderable.Merge(existing)
}
output[outRenderable.ID] = outRenderable
mapped[inRenderable.ID] = mapped[inRenderable.ID].Add(outRenderable.ID)
adjacencies[outRenderable.ID] = adjacencies[outRenderable.ID].Merge(inRenderable.Adjacency)
}
}
// Rewrite Adjacency for new node IDs.
for outNodeID, inAdjacency := range adjacencies {
outAdjacency := report.MakeIDList()
for _, inAdjacent := range inAdjacency {
outAdjacency = outAdjacency.Merge(mapped[inAdjacent])
}
outNode := output[outNodeID]
outNode.Adjacency = outAdjacency
output[outNodeID] = outNode
}
return Nodes{Nodes: output}
}
// Decorator transforms one renderer to another. e.g. Filters.
type Decorator func(Renderer) Renderer
// ComposeDecorators composes decorators into one.
func ComposeDecorators(decorators ...Decorator) Decorator {
return func(r Renderer) Renderer {
for _, decorator := range decorators {
r = decorator(r)
}
return r
}
}
type applyDecorator struct {
Renderer
}
func (ad applyDecorator) Render(rpt report.Report, dct Decorator) Nodes {
if dct != nil {
return dct(ad.Renderer).Render(rpt, nil)
}
return ad.Renderer.Render(rpt, nil)
}
// ApplyDecorator returns a renderer which will apply the given decorator to the child render.
func ApplyDecorator(renderer Renderer) Renderer {
return applyDecorator{renderer}
}
func propagateLatest(key string, from, to report.Node) report.Node {
if value, timestamp, ok := from.Latest.LookupEntry(key); ok {
to.Latest = to.Latest.Set(key, timestamp, value)
}
return to
}
// Condition is a predecate over the entire report that can evaluate to true or false.
type Condition func(report.Report) bool
type conditionalRenderer struct {
Condition
Renderer
}
// ConditionalRenderer renders nothing if the condition is false, otherwise it defers
// to the wrapped Renderer.
func ConditionalRenderer(c Condition, r Renderer) Renderer {
return conditionalRenderer{c, r}
}
func (cr conditionalRenderer) Render(rpt report.Report, dct Decorator) Nodes {
if cr.Condition(rpt) {
return cr.Renderer.Render(rpt, dct)
}
return Nodes{}
}
// ConstantRenderer renders a fixed set of nodes
type ConstantRenderer struct {
Nodes
}
// Render implements Renderer
func (c ConstantRenderer) Render(_ report.Report, _ Decorator) Nodes {
return c.Nodes
}
// joinResults is used by Renderers that join sets of nodes
type joinResults struct {
nodes report.Nodes
mapped map[string]string // input node ID -> output node ID
}
func newJoinResults() joinResults {
return joinResults{nodes: make(report.Nodes), mapped: map[string]string{}}
}
// Add Node M under id, creating a new result node if not already there
// and updating the mapping from old ID to new ID
// Note we do not update any counters for child topologies here, because addToResults
// is only ever called when m is an endpoint and we never look at endpoint counts
func (ret *joinResults) addToResults(m report.Node, id string, create func(string) report.Node) {
result, exists := ret.nodes[id]
if !exists {
result = create(id)
}
result.Children = result.Children.Add(m)
result.Children = result.Children.Merge(m.Children)
ret.nodes[id] = result
ret.mapped[m.ID] = id
}
// Rewrite Adjacency for new nodes in ret for original nodes in input
func (ret *joinResults) fixupAdjacencies(input Nodes) {
for _, n := range input.Nodes {
outID, ok := ret.mapped[n.ID]
if !ok {
continue
}
out := ret.nodes[outID]
// for each adjacency in the original node, find out what it maps to (if any),
// and add that to the new node
for _, a := range n.Adjacency {
if mappedDest, found := ret.mapped[a]; found {
out.Adjacency = out.Adjacency.Add(mappedDest)
}
}
ret.nodes[outID] = out
}
}
func (ret *joinResults) copyUnmatched(input Nodes) {
for _, n := range input.Nodes {
if _, found := ret.nodes[n.ID]; !found {
ret.nodes[n.ID] = n
}
}
}
func (ret *joinResults) result() Nodes {
return Nodes{Nodes: ret.nodes}
}