diff --git a/misc/dashboard/builder.sh b/misc/dashboard/builder.sh
index 0eaed8b344c..fb2e6defb5a 100644
--- a/misc/dashboard/builder.sh
+++ b/misc/dashboard/builder.sh
@@ -69,9 +69,7 @@ while true ; do
echo "Running benchmarks"
cd pkg || fatal "failed to cd to pkg"
make bench > ../../benchmarks 2>&1
- if [ $? -eq 0 ] ; then
- python ../../../buildcontrol.py benchmarks $BUILDER $rev ../../benchmarks || fatal "Cannot record benchmarks"
- fi
+ python ../../../buildcontrol.py benchmarks $BUILDER $rev ../../benchmarks || fatal "Cannot record benchmarks"
cd .. || fatal "failed to cd out of pkg"
fi
fi
diff --git a/misc/dashboard/godashboard/app.yaml b/misc/dashboard/godashboard/app.yaml
index 06681def10b..ec4d8d9c103 100644
--- a/misc/dashboard/godashboard/app.yaml
+++ b/misc/dashboard/godashboard/app.yaml
@@ -1,5 +1,5 @@
application: godashboard
-version: 1
+version: 3
runtime: python
api_version: 1
diff --git a/misc/dashboard/godashboard/benchmark1.html b/misc/dashboard/godashboard/benchmark1.html
new file mode 100644
index 00000000000..e174b249944
--- /dev/null
+++ b/misc/dashboard/godashboard/benchmark1.html
@@ -0,0 +1,107 @@
+
+
+
+ Go benchmarks
+
+
+
+
+
+ Go dashboard - {{benchmark}}
+
+ build status
+ benchmarks
+
+ {{benchmark}}
+ json
+
+ {% for g in graphs %}
+ {{g.builder}}
+ {% if g.url %}
+
+ {% else %}
+ (no data available)
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+ |
+ {% for b in builders %}
+ {{b.goos}} {{b.goarch}} {{b.note}} |
+ {% endfor %}
+ |
+ |
+ |
+
+
+ {% for r in revs %}
+
+ {{r.node|slice:":12"}} |
+
+ {% for ns in r.ns_by_builder %}
+
+ {% if ns %}
+ {{ns}}
+ {% endif %}
+ |
+ {% endfor %}
+ {{r.user|escape}} |
+ {{r.date|escape}} |
+ {{r.shortdesc|escape}} |
+
+ {% endfor %}
+
+
diff --git a/misc/dashboard/godashboard/benchmarks.html b/misc/dashboard/godashboard/benchmarks.html
new file mode 100644
index 00000000000..044bccc0563
--- /dev/null
+++ b/misc/dashboard/godashboard/benchmarks.html
@@ -0,0 +1,83 @@
+
+
+
+ Go benchmarks
+
+
+
+
+
+ Go dashboard - benchmarks
+
+ build status
+
+ Benchmarks
+
+
+ |
+ {% for b in builders %}
+ {{b.goos}} {{b.goarch}} {{b.note}} |
+ {% endfor %}
+
+
+ {% for m in benchmarks %}
+
+ {{m.name}} |
+
+ {% for b in m.builds %}
+
+ {% if b.url %}
+
+ {% endif %}
+ |
+ {% endfor %}
+
+ {% endfor %}
+
+
+
diff --git a/misc/dashboard/godashboard/gobuild.py b/misc/dashboard/godashboard/gobuild.py
index 32f95ca3d8c..c10d92dbdb8 100644
--- a/misc/dashboard/godashboard/gobuild.py
+++ b/misc/dashboard/godashboard/gobuild.py
@@ -5,6 +5,7 @@
# This is the server part of the continuous build system for Go. It must be run
# by AppEngine.
+from google.appengine.api import memcache
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
@@ -18,6 +19,7 @@ import os
import re
import struct
+# local imports
import key
# The majority of our state are commit objects. One of these exists for each of
@@ -44,6 +46,7 @@ class Commit(db.Model):
class Benchmark(db.Model):
name = db.StringProperty()
+ version = db.IntegerProperty()
class BenchmarkResult(db.Model):
num = db.IntegerProperty()
@@ -64,6 +67,15 @@ class Highwater(db.Model):
N = 30
+def builderInfo(b):
+ f = b.split('-', 3)
+ goos = f[0]
+ goarch = f[1]
+ note = ""
+ if len(f) > 2:
+ note = f[2]
+ return {'name': b, 'goos': goos, 'goarch': goarch, 'note': note}
+
class MainPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
@@ -77,13 +89,7 @@ class MainPage(webapp.RequestHandler):
for r in revs:
for b in r['builds']:
- f = b['builder'].split('-', 3)
- goos = f[0]
- goarch = f[1]
- note = ""
- if len(f) > 2:
- note = f[2]
- builders[b['builder']] = {'goos': goos, 'goarch': goarch, 'note': note}
+ builders[b['builder']] = builderInfo(b['builder'])
for r in revs:
have = set(x['builder'] for x in r['builds'])
@@ -91,7 +97,6 @@ class MainPage(webapp.RequestHandler):
for n in need:
r['builds'].append({'builder': n, 'log':'', 'ok': False})
r['builds'].sort(cmp = byBuilder)
- r['shortdesc'] = r['desc'].split('\n', 2)[0]
builders = list(builders.items())
builders.sort()
@@ -269,22 +274,131 @@ class Build(webapp.RequestHandler):
self.response.set_status(200)
class Benchmarks(webapp.RequestHandler):
- def get(self):
+ def json(self):
q = Benchmark.all()
+ q.filter('__key__ >', Benchmark.get_or_insert('v002.').key())
bs = q.fetch(10000)
self.response.set_status(200)
- self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
- self.response.out.write('{"benchmarks": [\n')
+ self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
+ self.response.out.write('{"benchmarks": [')
first = True
+ sep = "\n\t"
for b in bs:
- if not first:
- self.response.out.write(',"' + b.name + '"\n')
- else:
- self.response.out.write('"' + b.name + '"\n')
- first = False
- self.response.out.write(']}\n')
+ self.response.out.write('%s"%s"' % (sep, b.name))
+ sep = ",\n\t"
+ self.response.out.write('\n]}\n')
+
+ def get(self):
+ if self.request.get('fmt') == 'json':
+ return self.json()
+ self.response.set_status(200)
+ self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+ q = Commit.all()
+ q.order('-__key__')
+ n = q.fetch(1)[0]
+ key = "bench(%d)" % n.num
+ page = None # memcache.get(key)
+ if not page:
+ page = self.compute()
+ memcache.set(key, page, 3600)
+ self.response.out.write(page)
+
+ def compute(self):
+ q = Benchmark.all()
+ q.filter('__key__ >', Benchmark.get_or_insert('v002.').key())
+ bs = q.fetch(10000)
+
+ # Collect table giving all the data we need.
+ builders = {}
+ 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.ancestor(b)
+ q.order('-__key__')
+ results = q.fetch(10000)
+ m = {}
+ revs = {}
+ 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()
+
+ revs = list(revs.keys())
+ revs.sort()
+ first = revs[0]
+ 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
+ benchmarks = []
+ for name in names:
+ # Build list of cells, one per builder.
+ m = data[name]
+ builds = []
+ for builder in builders:
+ # Build cell: a URL for the chart server or an empty string.
+ if builder not in m:
+ builds.append({"url":""})
+ continue
+ d = m[builder]
+ max = 0
+ tot = 0
+ ntot = 0
+ for i in range(first, last+1):
+ if i not in d:
+ continue
+ val = d[i]
+ if max < val:
+ max = val
+ tot += val
+ ntot += 1
+ if max == 0:
+ builds.append({"url":""})
+ continue
+ avg = tot / ntot
+ if 2*avg > max:
+ max = 2*avg
+ # Encoding is 0-61, which is fine enough granularity for our tiny graphs. _ means missing.
+ encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+ s = ""
+ for i in range(first, last+1):
+ if i not in d:
+ s += "_"
+ continue
+ val = d[i]
+ s += encoding[int((len(encoding)-1)*val/max)]
+ builds.append({"url": "http://chart.apis.google.com/chart?cht=ls&chd=s:"+s})
+ benchmarks.append({"name": name, "builds": builds})
+
+ bs = []
+ for b in builders:
+ f = b.split('-', 3)
+ goos = f[0]
+ goarch = f[1]
+ note = ""
+ if len(f) > 2:
+ note = f[2]
+ bs.append({'goos': goos, 'goarch': goarch, 'note': note})
+
+ values = {"benchmarks": benchmarks, "builders": bs}
+
+ path = os.path.join(os.path.dirname(__file__), 'benchmarks.html')
+ return template.render(path, values)
def post(self):
if not auth(self.request):
@@ -327,24 +441,29 @@ class Benchmarks(webapp.RequestHandler):
return
for (benchmark, (iterations, time)) in benchmarks.items():
- b = Benchmark.get_or_insert(benchmark.encode('base64'), name = benchmark)
- r = BenchmarkResult(key_name = '%08x/builder' % n.num, parent = b, num = n.num, iterations = iterations, nsperop = time, builder = builder)
+ 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.put()
-
+ key = "bench(%d)" % n.num
+ memcache.delete(key)
self.response.set_status(200)
+def node(num):
+ q = Commit.all()
+ q.filter('num =', num)
+ n = q.get()
+ return n
+
class GetBenchmarks(webapp.RequestHandler):
def get(self):
- self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
- benchmark = self.request.path[12:].decode('hex').encode('base64')
-
- b = Benchmark.get_by_key_name(benchmark)
- if b is None:
+ benchmark = self.request.path[12:]
+ bm = Benchmark.get_by_key_name('v002.' + benchmark.encode('base64'))
+ if bm is None:
self.response.set_status(404)
return
q = BenchmarkResult.all()
- q.ancestor(b)
+ q.ancestor(bm)
q.order('-__key__')
results = q.fetch(10000)
@@ -352,26 +471,86 @@ class GetBenchmarks(webapp.RequestHandler):
self.response.set_status(404)
return
- max = -1
- min = 2000000000
+ maxv = -1
+ minv = 2000000000
builders = set()
for r in results:
- if max < r.num:
- max = r.num
- if min > r.num:
- min = r.num
+ if maxv < r.num:
+ maxv = r.num
+ if minv > r.num:
+ minv = r.num
builders.add(r.builder)
res = {}
for b in builders:
- res[b] = [[-1] * ((max - min) + 1), [-1] * ((max - min) + 1)]
+ res[b] = [[-1] * ((maxv - minv) + 1), [-1] * ((maxv - minv) + 1)]
for r in results:
- res[r.builder][0][r.num - min] = r.iterations
- res[r.builder][1][r.num - min] = r.nsperop
-
- self.response.out.write(str(res))
+ res[r.builder][0][r.num - minv] = r.iterations
+ res[r.builder][1][r.num - minv] = r.nsperop
+
+ minhash = node(minv).node
+ maxhash = node(maxv).node
+ if self.request.get('fmt') == 'json':
+ self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
+ self.response.out.write('{"min": "%s", "max": "%s", "data": {' % (minhash, maxhash))
+ sep = "\n\t"
+ for b in builders:
+ self.response.out.write('%s"%s": {"iterations": %s, "nsperop": %s}' % (sep, b, str(res[b][0]).replace("L", ""), str(res[b][1]).replace("L", "")))
+ sep = ",\n\t"
+ self.response.out.write("\n}}\n")
+ return
+ def bgraph(builder):
+ data = res[builder][1]
+ encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-"
+ m = max(data) # max ns timing
+ if m == -1:
+ return ""
+ tot = 0
+ ntot = 0
+ for d in data:
+ if d < 0:
+ continue
+ tot += d
+ ntot += 1
+ avg = tot / ntot
+ if 2*avg > m:
+ m = 2*avg
+ s = ""
+ for d in data:
+ if d < 0:
+ s += "__"
+ continue
+ val = int(d*4095.0/m)
+ s += encoding[val/64] + encoding[val%64]
+ return "http://chart.apis.google.com/chart?cht=lc&chxt=x,y&chxl=0:|%s|%s|1:|0|%g ns|%g ns&chd=e:%s" % (minhash[0:12], maxhash[0:12], m/2, m, s)
+
+ graphs = []
+ for b in builders:
+ graphs.append({"builder": b, "url": bgraph(b)})
+
+ revs = []
+ for i in range(minv, maxv+1):
+ r = nodeInfo(node(i))
+ ns = []
+ for b in builders:
+ t = res[b][1][i - minv]
+ if t < 0:
+ t = None
+ ns.append(t)
+ r["ns_by_builder"] = ns
+ revs.append(r)
+
+ path = os.path.join(os.path.dirname(__file__), 'benchmark1.html')
+ data = {
+ "benchmark": bm.name,
+ "builders": [builderInfo(b) for b in builders],
+ "graphs": graphs,
+ "revs": revs
+ }
+ self.response.out.write(template.render(path, data))
+
class FixedOffset(datetime.tzinfo):
"""Fixed offset in minutes east from UTC."""
@@ -430,13 +609,19 @@ def parseBuild(build):
[builder, logblob] = build.split('`')
return {'builder': builder, 'log': logblob, 'ok': len(logblob) == 0}
+def nodeInfo(c):
+ return {
+ "node": c.node,
+ "user": toUsername(c.user),
+ "date": dateToShortStr(c.date),
+ "desc": c.desc,
+ "shortdesc": c.desc.split('\n', 2)[0]
+ }
+
def toRev(c):
- b = { "node": c.node,
- "user": toUsername(c.user),
- "date": dateToShortStr(c.date),
- "desc": c.desc}
- b['builds'] = [parseBuild(build) for build in c.builds]
- return b
+ b = nodeInfo(c)
+ b['builds'] = [parseBuild(build) for build in c.builds]
+ return b
def byBuilder(x, y):
return cmp(x['builder'], y['builder'])
diff --git a/misc/dashboard/godashboard/main.html b/misc/dashboard/godashboard/main.html
index 388149ec312..7ba9aeed989 100644
--- a/misc/dashboard/godashboard/main.html
+++ b/misc/dashboard/godashboard/main.html
@@ -59,6 +59,8 @@
Go dashboard
+ benchmarks
+
Build status