-
Notifications
You must be signed in to change notification settings - Fork 714
/
Copy pathrender.go
271 lines (237 loc) · 7.59 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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()
}