Memoise & cache the result of renderers, so we don't recalculate views multiple times.

This commit is contained in:
Tom Wilkie
2016-01-22 15:05:01 -08:00
parent fd8696ffa2
commit b8daa02488
6 changed files with 201 additions and 156 deletions

View File

@@ -1,9 +1,35 @@
package render
import (
"fmt"
"reflect"
"github.com/bluele/gcache"
"github.com/weaveworks/scope/report"
)
var renderCache = gcache.New(100).LRU().Build()
func memoisedRender(r Renderer, rpt report.Report) RenderableNodes {
key := ""
v := reflect.ValueOf(r)
switch v.Kind() {
case reflect.Ptr, reflect.Func:
key = fmt.Sprintf("%s-%x", rpt.ID, v.Pointer())
default:
return r.Render(rpt)
}
if result, err := renderCache.Get(key); err == nil {
//fmt.Println("Hit", key)
return result.(RenderableNodes)
}
//fmt.Println("Miss", key)
output := r.Render(rpt)
renderCache.Set(key, output)
return output
}
// Renderer is something that can render a report to a set of RenderableNodes.
type Renderer interface {
Render(report.Report) RenderableNodes
@@ -27,22 +53,23 @@ type Reduce []Renderer
// MakeReduce is the only sane way to produce a Reduce Renderer.
func MakeReduce(renderers ...Renderer) Renderer {
return Reduce(renderers)
r := Reduce(renderers)
return &r
}
// Render produces a set of RenderableNodes given a Report.
func (r Reduce) Render(rpt report.Report) RenderableNodes {
func (r *Reduce) Render(rpt report.Report) RenderableNodes {
result := RenderableNodes{}
for _, renderer := range r {
result = result.Merge(renderer.Render(rpt))
for _, renderer := range *r {
result = result.Merge(memoisedRender(renderer, rpt))
}
return result
}
// Stats implements Renderer
func (r Reduce) Stats(rpt report.Report) Stats {
func (r *Reduce) Stats(rpt report.Report) Stats {
var result Stats
for _, renderer := range r {
for _, renderer := range *r {
result = result.merge(renderer.Stats(rpt))
}
return result
@@ -55,24 +82,29 @@ type Map struct {
Renderer
}
// MakeMap makes a new Map
func MakeMap(f MapFunc, r Renderer) Renderer {
return &Map{f, r}
}
// Render transforms a set of RenderableNodes produces by another Renderer.
// using a map function
func (m Map) Render(rpt report.Report) RenderableNodes {
func (m *Map) Render(rpt report.Report) RenderableNodes {
output, _ := m.render(rpt)
return output
}
// Stats implements Renderer
func (m Map) Stats(rpt report.Report) Stats {
func (m *Map) Stats(rpt report.Report) Stats {
// There doesn't seem to be an instance where we want stats to recurse
// through Maps - for instance we don't want to see the number of filtered
// processes in the container renderer.
return Stats{}
}
func (m Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) {
func (m *Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) {
var (
input = m.Renderer.Render(rpt)
input = memoisedRender(m.Renderer, rpt)
output = RenderableNodes{}
mapped = map[string]report.IDList{} // input node ID -> output node IDs
adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies