mirror of
https://github.com/golang/go
synced 2024-11-25 06:57:58 -07:00
dashboard: present benchmarks
* fix bug in benchmark collection: bad benchmark data key meant that all the builders collided when writing data. * report benchmarks even if make bench exits non-zero. * graphical and json presentations R=agl1 CC=golang-dev https://golang.org/cl/201065
This commit is contained in:
parent
95e08bde44
commit
d5b946f43d
@ -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
|
||||
cd .. || fatal "failed to cd out of pkg"
|
||||
fi
|
||||
fi
|
||||
|
@ -1,5 +1,5 @@
|
||||
application: godashboard
|
||||
version: 1
|
||||
version: 3
|
||||
runtime: python
|
||||
api_version: 1
|
||||
|
||||
|
107
misc/dashboard/godashboard/benchmark1.html
Normal file
107
misc/dashboard/godashboard/benchmark1.html
Normal file
@ -0,0 +1,107 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Go benchmarks</title>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.alternate {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.alternate tr td {
|
||||
padding-right: 10px;
|
||||
}
|
||||
table.alternate tr td:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
table.alternate tr:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
td.revision {
|
||||
}
|
||||
span.hash {
|
||||
font-family: monospace;
|
||||
font-size: small;
|
||||
color: #aaa;
|
||||
}
|
||||
span.nodeseq {
|
||||
}
|
||||
td.user {
|
||||
font-size: small;
|
||||
}
|
||||
td.date {
|
||||
color: #aaa;
|
||||
font-size: small;
|
||||
}
|
||||
td.result {
|
||||
text-align: center;
|
||||
}
|
||||
span.ok {
|
||||
}
|
||||
td.desc {
|
||||
font-size: small;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
th.builder {
|
||||
font-weight: bold;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Go dashboard - {{benchmark}}</h1>
|
||||
|
||||
<a href="/">build status</a> <br>
|
||||
<a href="/benchmarks">benchmarks</a>
|
||||
|
||||
<h2>{{benchmark}}</h2>
|
||||
<a href="{{benchmark}}?fmt=json">json</a>
|
||||
|
||||
{% for g in graphs %}
|
||||
<h3>{{g.builder}}</h3>
|
||||
{% if g.url %}
|
||||
<img src="{{g.url}}&chs=600x150&chf=bg,s,00000000&chco=000000ff&chls=1,1,0">
|
||||
{% else %}
|
||||
(no data available)
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<br><br>
|
||||
|
||||
<table class="alternate" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<th></th>
|
||||
{% for b in builders %}
|
||||
<th class="builder">{{b.goos}}<br>{{b.goarch}}<br>{{b.note}}</th>
|
||||
{% endfor %}
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
{% for r in revs %}
|
||||
<tr>
|
||||
<td class="revision"><span class="hash"><a href="https://code.google.com/p/go/source/detail?r={{r.node}}">{{r.node|slice:":12"}}</a></span></td>
|
||||
|
||||
{% for ns in r.ns_by_builder %}
|
||||
<td class="result">
|
||||
{% if ns %}
|
||||
{{ns}}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="user">{{r.user|escape}}</td>
|
||||
<td class="date">{{r.date|escape}}</td>
|
||||
<td class="desc">{{r.shortdesc|escape}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
83
misc/dashboard/godashboard/benchmarks.html
Normal file
83
misc/dashboard/godashboard/benchmarks.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Go benchmarks</title>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.alternate {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.alternate tr td {
|
||||
padding-right: 10px;
|
||||
}
|
||||
table.alternate tr td:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
table.alternate tr:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
td.revision {
|
||||
}
|
||||
span.nodeseq {
|
||||
}
|
||||
td.user {
|
||||
font-size: small;
|
||||
}
|
||||
td.date {
|
||||
color: #aaa;
|
||||
font-size: small;
|
||||
}
|
||||
td.result {
|
||||
text-align: center;
|
||||
}
|
||||
span.ok {
|
||||
}
|
||||
td.desc {
|
||||
font-size: small;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
th.builder {
|
||||
font-weight: bold;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Go dashboard - benchmarks</h1>
|
||||
|
||||
<a href="/">build status</a>
|
||||
|
||||
<h2>Benchmarks</h2>
|
||||
<table class="alternate" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<th></th>
|
||||
{% for b in builders %}
|
||||
<th class="builder">{{b.goos}}<br>{{b.goarch}}<br>{{b.note}}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
{% for m in benchmarks %}
|
||||
<tr>
|
||||
<td class="name"><a href="/benchmarks/{{m.name}}">{{m.name}}</a></td>
|
||||
|
||||
{% for b in m.builds %}
|
||||
<td class="result">
|
||||
{% if b.url %}
|
||||
<img src="{{b.url}}&chs=80x20&chf=bg,s,00000000&chco=000000ff&chls=1,1,0">
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -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,25 +471,85 @@ 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
|
||||
res[r.builder][0][r.num - minv] = r.iterations
|
||||
res[r.builder][1][r.num - minv] = r.nsperop
|
||||
|
||||
self.response.out.write(str(res))
|
||||
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,11 +609,17 @@ def parseBuild(build):
|
||||
[builder, logblob] = build.split('`')
|
||||
return {'builder': builder, 'log': logblob, 'ok': len(logblob) == 0}
|
||||
|
||||
def toRev(c):
|
||||
b = { "node": c.node,
|
||||
def nodeInfo(c):
|
||||
return {
|
||||
"node": c.node,
|
||||
"user": toUsername(c.user),
|
||||
"date": dateToShortStr(c.date),
|
||||
"desc": c.desc}
|
||||
"desc": c.desc,
|
||||
"shortdesc": c.desc.split('\n', 2)[0]
|
||||
}
|
||||
|
||||
def toRev(c):
|
||||
b = nodeInfo(c)
|
||||
b['builds'] = [parseBuild(build) for build in c.builds]
|
||||
return b
|
||||
|
||||
|
@ -59,6 +59,8 @@
|
||||
<body>
|
||||
<h1>Go dashboard</h1>
|
||||
|
||||
<a href="benchmarks">benchmarks</a>
|
||||
|
||||
<h2>Build status</h2>
|
||||
<table class="alternate" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
|
Loading…
Reference in New Issue
Block a user