mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
These can be long-running operations, and if the client retries we get the cancelled one running in parallel with the retry, slowing both down and making it likely the next one will time out too.
272 lines
7.6 KiB
Go
272 lines
7.6 KiB
Go
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()
|
|
}
|