--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/requirements.txt Sat May 02 01:52:26 2015 +0800
--- a/settings.py Sat May 02 01:47:27 2015 +0800
+++ b/settings.py Sat May 02 01:52:26 2015 +0800
LOCKFILE = rel('data', 'bench.lock')
TESTHGREPO = rel('data', 'testhg')
TESTREPO = rel('data', 'testrepo')
+TEMPLATES = rel('templates')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/results.html Sat May 02 01:52:26 2015 +0800
+ <title>hgperfcheck{% if changesets %} (rev {{ changesets[0]['rev'] }} and earlier){% end %}</title>
+ <style type="text/css">
+ font-family: monospace;
+ background-color: silver;
+ text-overflow: ellipsis;
+ <a href="/results.html">back to tip</a>
+ {% set prevrev = changesets[0]['rev'] + 120 if changesets else None %}
+ {% if prevrev %}<a href="?rev=rev({{ prevrev }})">prev page</a>{% end %}
+ {% set nextrev = changesets[-1]['rev'] - 1 if changesets else None %}
+ {% if nextrev > -1 %}<a href="?rev=rev({{ nextrev }})">next page</a>{% end %}
+ <td title="merge">M</td>
+ {% for mark, desc in marks.items() %}
+ <td title="{{ desc }}">{{ mark }}</td>
+ {% for cset in changesets %}
+ <td>{{ cset['rev'] }}</td>
+ <td><a href="http://selenic.com/hg/rev/{{ cset['node'] }}">{{ cset['node'][:12] }}<a/></td>
+ <td>{{ 'M' if len(cset['parents']) > 1 else '' }}</td>
+ <td>{{ cset['branch'] }}</td>
+ <td>{{ ', '.join(cset['tags']) }}</td>
+ <td title="{{ cset['user'] }}">{{ cset['user'].partition(' <')[0] }}</td>
+ <td class="hint"><div title="{{ cset['desc'] }}">{{ cset['desc'].partition('\n')[0] }}</div></td>
+ {% for mark in marks %}
+ {% set result = results.get(cset['node'], {}) %}
+ {% set value, color = result.get(mark, (None, None)) %}
+ <span style="{% if color is not None %}background-color: rgb{{ str(color) }}{% end %}"> </span>
+ <span title="{{ value }}">{{ '{:.2f}s'.format(value) if value is not None else 'N/A' }}</span>
+ <a href="/results.html">back to tip</a>
+ {% if prevrev %}<a href="?rev=rev({{ prevrev }})">prev page</a>{% end %}
+ {% if nextrev > -1 %}<a href="?rev=rev({{ nextrev }})">next page</a>{% end %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/viewer.py Sat May 02 01:52:26 2015 +0800
+from tornado.ioloop import IOLoop
+from tornado.options import define, options
+from tornado.web import Application, RequestHandler, URLSpec
+from settings import DBPATH, HG, TEMPLATES, TESTHGREPO
+define('listen', metavar='IP', default='127.0.0.1')
+define('port', metavar='PORT', default=8065, type=int)
+define('xheaders', metavar='True|False', default=False, type=bool)
+define('debug', metavar='True|False', default=False, type=bool)
+ output = subprocess.check_output([HG, 'log', '-R', TESTHGREPO, '-T', 'json', '-r', revset])
+ return json.loads(output)
+class BaseHandler(RequestHandler):
+ self.conn = sqlite3.connect(DBPATH)
+ super(BaseHandler, self).prepare()
+class IndexHandler(BaseHandler):
+ self.redirect('results.html')
+class ResultsHandler(BaseHandler):
+ rev = self.get_argument('rev', 'tip')
+ mark: self.conn.execute(
+ 'SELECT MIN(time), MAX(time) FROM results WHERE mark = ? AND cache = ?',
+ (mark, False)).fetchone()
+ changesets = getinfo('first(%s:0, %d)' % (rev, revcount))
+ for cset in changesets:
+ resultsq = self.conn.execute(
+ 'SELECT mark, time FROM results WHERE node = ? AND cache = ?',
+ for mark, time in resultsq:
+ color = green_to_red(limits[mark], time) if time is not None else None
+ results.setdefault(node, {})[mark] = (time, color)
+ return changesets, results
+ self.set_header('Content-Type', 'text/plain; charset=UTF-8')
+ changesets, results = self.getdata()
+ for cset in changesets:
+ self.write('%(rev)s\t%(node)s\t' % cset)
+ str(results.get(cset['node'], {}).get(mark, ('',))[0])
+ self.set_header('Content-Type', 'text/plain; charset=UTF-8')
+ rev = self.get_argument('rev', 'tip')
+ regex = re.compile(r'^[-\\|/+ ]+([0-9a-f]{40})$')
+ template = '{rev}:{node|short} {tags} {date|isodate} {author|user} {desc|firstline|strip}\n {node}\n\n'
+ revset = 'first(%s:0, %d)' % (rev, revcount)
+ cmd = [HG, 'log', '-R', TESTHGREPO, '-G', '-T', template, '-r', revset]
+ graph = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ while graph.poll() is None:
+ line = graph.stdout.readline()
+ matches = regex.findall(line)
+ rows = self.conn.execute('SELECT mark, time FROM results WHERE node = ?', (match,))
+ msg.append('{}: {:.2g}s'.format(*row))
+ line = line.replace(match, ' '.join(msg))
+ def results_html(self):
+ changesets, results = self.getdata()
+ self.render('results.html', changesets=changesets, marks=MARKS, results=results)
+def green_to_red((low, high), value):
+ hue = (value - low) / (high - low) if high != low else 0.5
+ r, g, b = colorsys.hsv_to_rgb((1 - hue) * 0.3, 1, 1)
+ return (int(r * 255), int(g * 255), int(b * 255))
+class Viewer(Application):
+ URLSpec(r'/', IndexHandler),
+ URLSpec(r'/results\.(html|tsv|asc)', ResultsHandler),
+ template_path=TEMPLATES,
+ super(Viewer, self).__init__(handlers, **settings)
+ def listen(self, port, address='', **kwargs):
+ logging.info('{} is serving on {}:{}'.format(self.__class__.__name__, address, port))
+ super(Viewer, self).listen(port, address, **kwargs)
+ options.parse_command_line()
+ application.listen(options.port, address=options.listen, xheaders=options.xheaders)
+ IOLoop.instance().start()
+if __name__ == '__main__':