123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- package render
- import (
- "context"
- opentracing "github.com/opentracing/opentracing-go"
- otlog "github.com/opentracing/opentracing-go/log"
- "github.com/weaveworks/scope/report"
- )
- // MapFunc is anything which can take an arbitrary Node and
- // return another Node.
- //
- // If the output ID is blank, the node shall be omitted from the rendered topology.
- // (we chose not to return an extra bool because it adds clutter)
- type MapFunc func(report.Node) report.Node
- // Renderer is something that can render a report to a set of Nodes.
- type Renderer interface {
- Render(context.Context, report.Report) 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,
- }
- }
- // Transformer is something that transforms one set of Nodes to
- // another set of Nodes.
- type Transformer interface {
- Transform(nodes Nodes) Nodes
- }
- // Transformers is a composition of Transformers
- type Transformers []Transformer
- // Transform implements Transformer
- func (ts Transformers) Transform(nodes Nodes) Nodes {
- for _, t := range ts {
- nodes = t.Transform(nodes)
- }
- return nodes
- }
- // Render renders the report and then transforms it
- func Render(ctx context.Context, rpt report.Report, renderer Renderer, transformer Transformer) Nodes {
- span, ctx := opentracing.StartSpanFromContext(ctx, "Render:"+typeName(renderer))
- defer span.Finish()
- return transformer.Transform(renderer.Render(ctx, rpt))
- }
- // 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(ctx context.Context, rpt report.Report) Nodes {
- if ctx.Err() != nil {
- return Nodes{}
- }
- span, ctx := opentracing.StartSpanFromContext(ctx, "Reduce.Render")
- defer span.Finish()
- l := len(r)
- switch l {
- case 0:
- return Nodes{}
- }
- c := make(chan Nodes, l)
- for _, renderer := range r {
- renderer := renderer // Pike!!
- go func() {
- span, ctx := opentracing.StartSpanFromContext(ctx, typeName(renderer))
- c <- renderer.Render(ctx, rpt)
- span.Finish()
- }()
- }
- 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(ctx context.Context, rpt report.Report) Nodes {
- span, ctx := opentracing.StartSpanFromContext(ctx, "Map.Render:"+functionName(m.MapFunc))
- defer span.Finish()
- var (
- input = m.Renderer.Render(ctx, rpt)
- output = newJoinResults(nil)
- )
- // Rewrite all the nodes according to the map function
- for _, inRenderable := range input.Nodes {
- if ctx.Err() != nil { // check if cancelled
- return Nodes{}
- }
- outRenderable := m.MapFunc(inRenderable)
- if outRenderable.ID != "" {
- output.add(inRenderable.ID, outRenderable)
- }
- }
- span.LogFields(otlog.Int("input.nodes", len(input.Nodes)),
- otlog.Int("ouput.nodes", len(output.nodes)))
- return output.result(ctx, input)
- }
- // 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(ctx context.Context, rpt report.Report) Nodes {
- if cr.Condition(rpt) {
- return cr.Renderer.Render(ctx, rpt)
- }
- return 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 - common case
- multi map[string][]string // input node ID -> output node IDs - exceptional case
- }
- func newJoinResults(inputNodes report.Nodes) joinResults {
- nodes := make(report.Nodes, len(inputNodes))
- for id, n := range inputNodes {
- n.Adjacency = nil // result() assumes all nodes start with no adjacencies
- n.Children = n.Children.Copy() // so we can do unsafe adds
- nodes[id] = n
- }
- return joinResults{nodes: nodes, mapped: map[string]string{}, multi: map[string][]string{}}
- }
- func (ret *joinResults) mapChild(from, to string) {
- if _, ok := ret.mapped[from]; !ok {
- ret.mapped[from] = to
- } else {
- ret.multi[from] = append(ret.multi[from], to)
- }
- }
- // Add m into the results as a top-level node, mapped from original ID
- // Note it is not safe to mix calls to add() with addChild(), addChildAndChildren() or addUnmappedChild()
- func (ret *joinResults) add(from string, m report.Node) {
- if existing, ok := ret.nodes[m.ID]; ok {
- m = m.Merge(existing)
- }
- ret.nodes[m.ID] = m
- ret.mapChild(from, m.ID)
- }
- // Add m as a child of the node at id, creating a new result node in
- // the specified topology if not already there.
- func (ret *joinResults) addUnmappedChild(m report.Node, id string, topology string) {
- result, exists := ret.nodes[id]
- if !exists {
- result = report.MakeNode(id).WithTopology(topology)
- }
- result.Children.UnsafeAdd(m)
- if m.Topology != report.Endpoint { // optimisation: we never look at endpoint counts
- result = result.AddCounter(m.Topology, 1)
- }
- ret.nodes[id] = result
- }
- // Add m as a child of the node at id, creating a new result node in
- // the specified topology if not already there, and updating the
- // mapping from old ID to new ID.
- func (ret *joinResults) addChild(m report.Node, id string, topology string) {
- ret.addUnmappedChild(m, id, topology)
- ret.mapChild(m.ID, id)
- }
- // Like addChild, but also add m's children.
- func (ret *joinResults) addChildAndChildren(m report.Node, id string, topology string) {
- ret.addUnmappedChild(m, id, topology)
- result := ret.nodes[id]
- result.Children.UnsafeMerge(m.Children)
- ret.nodes[id] = result
- ret.mapChild(m.ID, id)
- }
- // Add a copy of n straight into the results
- func (ret *joinResults) passThrough(n report.Node) {
- n.Adjacency = nil // result() assumes all nodes start with no adjacencies
- ret.nodes[n.ID] = n
- n.Children = n.Children.Copy() // so we can do unsafe adds
- ret.mapChild(n.ID, n.ID)
- }
- // Rewrite Adjacency of nodes in ret mapped from original nodes in
- // input, and return the result.
- func (ret *joinResults) result(ctx context.Context, input Nodes) Nodes {
- for _, n := range input.Nodes {
- if ctx.Err() != nil { // check if cancelled
- return Nodes{}
- }
- outID, ok := ret.mapped[n.ID]
- if !ok {
- continue
- }
- ret.rewriteAdjacency(outID, n.Adjacency)
- for _, outID := range ret.multi[n.ID] {
- ret.rewriteAdjacency(outID, n.Adjacency)
- }
- }
- return Nodes{Nodes: ret.nodes}
- }
- func (ret *joinResults) rewriteAdjacency(outID string, adjacency report.IDList) {
- 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 adjacency {
- if mappedDest, found := ret.mapped[a]; found {
- out.Adjacency = out.Adjacency.Add(mappedDest)
- out.Adjacency = out.Adjacency.Add(ret.multi[a]...)
- }
- }
- ret.nodes[outID] = out
- }
- // ResetCache blows away the rendered node cache, and known service
- // cache.
- func ResetCache() {
- renderCache.Purge()
- purgeKnownServiceCache()
- }
|