Commit 666abfb6 authored by Russ Cox's avatar Russ Cox

dashboard: lots of caching to avoid datastore queries

reorganize benchmark computation so that it is
incremental.  if it times out, it doesn't lose the
pieces it already computed, so that next time it
has a fighting chance to finish.

R=agl1, agl
CC=golang-dev
https://golang.org/cl/216046
parent 32c3c953
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
# by AppEngine. # by AppEngine.
from google.appengine.api import memcache from google.appengine.api import memcache
from google.appengine.runtime import DeadlineExceededError
from google.appengine.ext import db from google.appengine.ext import db
from google.appengine.ext import webapp from google.appengine.ext import webapp
from google.appengine.ext.webapp import template from google.appengine.ext.webapp import template
...@@ -76,6 +77,15 @@ def builderInfo(b): ...@@ -76,6 +77,15 @@ def builderInfo(b):
note = f[2] note = f[2]
return {'name': b, 'goos': goos, 'goarch': goarch, 'note': note} return {'name': b, 'goos': goos, 'goarch': goarch, 'note': note}
def builderset():
q = Commit.all()
q.order('-__key__')
results = q.fetch(N)
builders = set()
for c in results:
builders.update(set(parseBuild(build)['builder'] for build in c.builds))
return builders
class MainPage(webapp.RequestHandler): class MainPage(webapp.RequestHandler):
def get(self): def get(self):
self.response.headers['Content-Type'] = 'text/html; charset=utf-8' self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
...@@ -109,6 +119,9 @@ class GetHighwater(webapp.RequestHandler): ...@@ -109,6 +119,9 @@ class GetHighwater(webapp.RequestHandler):
def get(self): def get(self):
builder = self.request.get('builder') builder = self.request.get('builder')
key = 'hw-%s' % builder
node = memcache.get(key)
if node is None:
hw = Highwater.get_by_key_name('hw-%s' % builder) hw = Highwater.get_by_key_name('hw-%s' % builder)
if hw is None: if hw is None:
# If no highwater has been recorded for this builder, # If no highwater has been recorded for this builder,
...@@ -116,10 +129,8 @@ class GetHighwater(webapp.RequestHandler): ...@@ -116,10 +129,8 @@ class GetHighwater(webapp.RequestHandler):
q = Commit.all() q = Commit.all()
q.order('-__key__') q.order('-__key__')
c = q.fetch(N+1)[-1] c = q.fetch(N+1)[-1]
self.response.set_status(200) node = c.node
self.response.out.write(c.node) else:
return
# if the proposed hw is too old, bump it forward # if the proposed hw is too old, bump it forward
node = hw.commit node = hw.commit
found = False found = False
...@@ -132,6 +143,7 @@ class GetHighwater(webapp.RequestHandler): ...@@ -132,6 +143,7 @@ class GetHighwater(webapp.RequestHandler):
break break
if not found: if not found:
node = recent[-1].node node = recent[-1].node
memcache.set(key, node, 3600)
self.response.set_status(200) self.response.set_status(200)
self.response.out.write(node) self.response.out.write(node)
...@@ -166,7 +178,9 @@ class SetHighwater(webapp.RequestHandler): ...@@ -166,7 +178,9 @@ class SetHighwater(webapp.RequestHandler):
if not found: if not found:
c = recent[-1] c = recent[-1]
hw = Highwater(key_name = 'hw-%s' % builder) key = 'hw-%s' % builder
memcache.delete(key)
hw = Highwater(key_name = key)
hw.commit = c.node hw.commit = c.node
hw.put() hw.put()
...@@ -270,6 +284,8 @@ class Build(webapp.RequestHandler): ...@@ -270,6 +284,8 @@ class Build(webapp.RequestHandler):
hw = Highwater(key_name = 'hw-%s' % builder) hw = Highwater(key_name = 'hw-%s' % builder)
hw.commit = node hw.commit = node
hw.put() hw.put()
memcache.delete('hw')
memcache.delete('bench')
self.response.set_status(200) self.response.set_status(200)
...@@ -295,95 +311,91 @@ class Benchmarks(webapp.RequestHandler): ...@@ -295,95 +311,91 @@ class Benchmarks(webapp.RequestHandler):
return self.json() return self.json()
self.response.set_status(200) self.response.set_status(200)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8' self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
page = memcache.get('bench')
if not page:
num = memcache.get('hw')
if num is None:
q = Commit.all() q = Commit.all()
q.order('-__key__') q.order('-__key__')
n = q.fetch(1)[0] n = q.fetch(1)[0]
key = "bench(%d)" % n.num memcache.set('hw', num)
page = None # memcache.get(key) page, full = self.compute(n.num)
if not page: if full:
page = self.compute() memcache.set('bench', page, 3600)
memcache.set(key, page, 3600)
self.response.out.write(page) self.response.out.write(page)
def compute(self): def compute(self, num):
q = Benchmark.all() q = Benchmark.all()
q.filter('__key__ >', Benchmark.get_or_insert('v002.').key()) q.filter('__key__ >', Benchmark.get_or_insert('v002.').key())
bs = q.fetch(10000) benchmarks = q.fetch(10000)
# Collect table giving all the data we need. # Which builders have sent benchmarks recently?
builders = {} builders = set()
data = {}
for b in bs:
# TODO(rsc): Will want to limit benchmarks to a certain
# number of commits eventually, but there aren't enough
# commits yet to worry.
q = BenchmarkResult.all() q = BenchmarkResult.all()
q.ancestor(b) q.ancestor(benchmarks[0])
q.order('-__key__') q.order('-__key__')
results = q.fetch(10000) for r in q.fetch(50):
m = {} builders.add(r.builder)
revs = {} builders = list(builders)
for r in results:
if r.builder not in m:
m[r.builder] = {}
m[r.builder][r.num] = r.nsperop
revs[r.num] = 0
builders[r.builder] = 0
data[b.name] = m
builders = list(builders.keys())
builders.sort() builders.sort()
revs = list(revs.keys()) NB = 80
revs.sort() last = num
first = revs[0] first = num+1 - NB
last = revs[-1]
if len(revs) > 80: # At most 80 commits back
last = revs[-80]
names = list(data.keys())
names.sort()
# Build list of rows, one per benchmark # Build list of rows, one per benchmark
benchmarks = [] rows = [{"name": bm.name, "builds": [{"url": ""} for b in builders]} for bm in benchmarks]
for name in names:
# Build list of cells, one per builder. full = True
m = data[name] try:
builds = [] for i in range(len(rows)):
for builder in builders: data = None
bm = benchmarks[i]
builds = rows[i]["builds"]
all = None
for j in range(len(builders)):
cell = builds[j]
b = builders[j]
# Build cell: a URL for the chart server or an empty string. # Build cell: a URL for the chart server or an empty string.
if builder not in m: # Cache individual graphs because they're so damn expensive.
builds.append({"url":""}) key = "bench(%s,%s,%d)" % (bm.name, b, num)
continue url = memcache.get(key)
d = m[builder] if url is not None:
max = 0 cell["url"] = url
tot = 0
ntot = 0
for i in range(first, last+1):
if i not in d:
continue continue
val = d[i]
if max < val: # Page in all data for benchmark for all builders,
max = val # on demand. It might be faster to ask for just the
tot += val # builder that we need, but q.filter('builder = ', b) is
ntot += 1 # broken right now (the index is corrupt).
if max == 0: if all is None:
builds.append({"url":""}) q = BenchmarkResult.all()
q.ancestor(bm)
q.order('-__key__')
all = q.fetch(1000)
data = [-1 for x in range(first, last+1)]
for r in all:
if r.builder == b and first <= r.num and r.num <= last:
data[r.num - first] = r.nsperop
present = [x for x in data if x >= 0]
if len(present) == 0:
memcache.set(key, "", 3600)
continue continue
avg = tot / ntot avg = sum(present) / len(present)
if 2*avg > max: maxval = max(2*avg, max(present))
max = 2*avg
# Encoding is 0-61, which is fine enough granularity for our tiny graphs. _ means missing. # Encoding is 0-61, which is fine enough granularity for our tiny graphs. _ means missing.
encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
s = "" s = ''.join([x < 0 and "_" or encoding[int((len(encoding)-1)*x/maxval)] for x in data])
for i in range(first, last+1): url = "http://chart.apis.google.com/chart?cht=ls&chd=s:"+s
if i not in d: memcache.set(key, url, 3600)
s += "_" cell["url"] = url
continue except DeadlineExceededError:
val = d[i] # forge ahead with partial benchmark results
s += encoding[int((len(encoding)-1)*val/max)] # the url caches above should make the next page quicker to compute
builds.append({"url": "http://chart.apis.google.com/chart?cht=ls&chd=s:"+s}) full = False
benchmarks.append({"name": name, "builds": builds})
names = [bm.name for bm in benchmarks]
bs = [] bs = []
for b in builders: for b in builders:
...@@ -395,10 +407,10 @@ class Benchmarks(webapp.RequestHandler): ...@@ -395,10 +407,10 @@ class Benchmarks(webapp.RequestHandler):
note = f[2] note = f[2]
bs.append({'goos': goos, 'goarch': goarch, 'note': note}) bs.append({'goos': goos, 'goarch': goarch, 'note': note})
values = {"benchmarks": benchmarks, "builders": bs} values = {"benchmarks": rows, "builders": bs}
path = os.path.join(os.path.dirname(__file__), 'benchmarks.html') path = os.path.join(os.path.dirname(__file__), 'benchmarks.html')
return template.render(path, values) return template.render(path, values), full
def post(self): def post(self):
if not auth(self.request): if not auth(self.request):
...@@ -444,8 +456,10 @@ class Benchmarks(webapp.RequestHandler): ...@@ -444,8 +456,10 @@ class Benchmarks(webapp.RequestHandler):
b = Benchmark.get_or_insert('v002.' + benchmark.encode('base64'), name = benchmark, version = 2) b = Benchmark.get_or_insert('v002.' + benchmark.encode('base64'), name = benchmark, version = 2)
r = BenchmarkResult(key_name = '%08x/%s' % (n.num, builder), parent = b, num = n.num, iterations = iterations, nsperop = time, builder = builder) r = BenchmarkResult(key_name = '%08x/%s' % (n.num, builder), parent = b, num = n.num, iterations = iterations, nsperop = time, builder = builder)
r.put() r.put()
key = "bench(%d)" % n.num key = "bench(%s,%s,%d)" % (benchmark, builder, n.num)
memcache.delete(key) memcache.delete(key)
memcache.delete('bench')
self.response.set_status(200) self.response.set_status(200)
def node(num): def node(num):
...@@ -481,6 +495,9 @@ class GetBenchmarks(webapp.RequestHandler): ...@@ -481,6 +495,9 @@ class GetBenchmarks(webapp.RequestHandler):
minv = r.num minv = r.num
builders.add(r.builder) builders.add(r.builder)
builders = list(builders)
builders.sort()
res = {} res = {}
for b in builders: for b in builders:
res[b] = [[-1] * ((maxv - minv) + 1), [-1] * ((maxv - minv) + 1)] res[b] = [[-1] * ((maxv - minv) + 1), [-1] * ((maxv - minv) + 1)]
......
indexes: indexes:
# AUTOGENERATED - kind: BenchmarkResult
ancestor: yes
# This index.yaml is automatically updated whenever the dev_appserver properties:
# detects that a new type of query is run. If you want to manage the - name: builder
# index.yaml file manually, remove the above marker line (the line - name: __key__
# saying "# AUTOGENERATED"). If you want to manage some indexes direction: desc
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
- kind: BenchmarkResult - kind: BenchmarkResult
ancestor: yes ancestor: yes
...@@ -20,3 +17,14 @@ indexes: ...@@ -20,3 +17,14 @@ indexes:
properties: properties:
- name: __key__ - name: __key__
direction: desc direction: desc
# AUTOGENERATED
# This index.yaml is automatically updated whenever the dev_appserver
# detects that a new type of query is run. If you want to manage the
# index.yaml file manually, remove the above marker line (the line
# saying "# AUTOGENERATED"). If you want to manage some indexes
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment