Download:
child 2:201f7e7e5ec8
parent 0:3ddb9df55b5d
1:cb057921cd8c
Anton Shestakov <engored@ya.ru>, Sat, 02 May 2015 01:52:26 +0800
viewer webapp

4 файлов изменено, 230 вставок(+), 0 удалений(-) [+]
requirements.txt file | annotate | diff | comparison | revisions
settings.py file | annotate | diff | comparison | revisions
templates/results.html file | annotate | diff | comparison | revisions
viewer.py file | annotate | diff | comparison | revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/requirements.txt Sat May 02 01:52:26 2015 +0800
@@ -0,0 +1,1 @@
+tornado==4.1
--- a/settings.py Sat May 02 01:47:27 2015 +0800
+++ b/settings.py Sat May 02 01:52:26 2015 +0800
@@ -12,3 +12,6 @@
LOCKFILE = rel('data', 'bench.lock')
TESTHGREPO = rel('data', 'testhg')
TESTREPO = rel('data', 'testrepo')
+
+# webapp templates
+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
@@ -0,0 +1,80 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>hgperfcheck{% if changesets %} (rev {{ changesets[0]['rev'] }} and earlier){% end %}</title>
+ <style type="text/css">
+ * {
+ font-family: monospace;
+ }
+ table {
+ border-spacing: 0;
+ }
+ tbody tr:hover {
+ background-color: silver;
+ }
+ td {
+ white-space: nowrap;
+ padding: 1px 5px;
+ }
+ td.hint {
+ width: 100%;
+ position: relative;
+ }
+ td.hint div {
+ position: absolute;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ top: 1px;
+ left: 5px;
+ right: 5px;
+ }
+ </style>
+ </head>
+ <body>
+ <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 %}
+ <table>
+ <thead>
+ <tr>
+ <td>rev</td>
+ <td>node</td>
+ <td title="merge">M</td>
+ <td>branch</td>
+ <td>tags</td>
+ <td>user</td>
+ <td>desc</td>
+ {% for mark, desc in marks.items() %}
+ <td title="{{ desc }}">{{ mark }}</td>
+ {% end %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for cset in changesets %}
+ <tr>
+ <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)) %}
+ <td>
+ <span style="{% if color is not None %}background-color: rgb{{ str(color) }}{% end %}">&nbsp;</span>
+ <span title="{{ value }}">{{ '{:.2f}s'.format(value) if value is not None else 'N/A' }}</span>
+ </td>
+ {% end %}
+ </tr>
+ {% end %}
+ </tbody>
+ </table>
+ <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 %}
+ </body>
+</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/viewer.py Sat May 02 01:52:26 2015 +0800
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+import colorsys
+import json
+import logging
+import re
+import sqlite3
+import subprocess
+
+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
+from bench import MARKS
+
+
+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)
+
+
+def getinfo(revset):
+ output = subprocess.check_output([HG, 'log', '-R', TESTHGREPO, '-T', 'json', '-r', revset])
+ return json.loads(output)
+
+
+class BaseHandler(RequestHandler):
+ def prepare(self):
+ self.conn = sqlite3.connect(DBPATH)
+ super(BaseHandler, self).prepare()
+
+ def on_finish(self):
+ self.conn.close()
+
+
+class IndexHandler(BaseHandler):
+ def get(self):
+ self.redirect('results.html')
+
+
+class ResultsHandler(BaseHandler):
+ def get(self, ext):
+ if ext == 'tsv':
+ self.results_tsv()
+ elif ext == 'asc':
+ self.results_asc()
+ elif ext == 'html':
+ self.results_html()
+
+ def getdata(self):
+ rev = self.get_argument('rev', 'tip')
+ revcount = 120
+ results = {}
+ limits = {
+ mark: self.conn.execute(
+ 'SELECT MIN(time), MAX(time) FROM results WHERE mark = ? AND cache = ?',
+ (mark, False)).fetchone()
+ for mark in MARKS
+ }
+
+ changesets = getinfo('first(%s:0, %d)' % (rev, revcount))
+
+ for cset in changesets:
+ node = cset['node']
+ resultsq = self.conn.execute(
+ 'SELECT mark, time FROM results WHERE node = ? AND cache = ?',
+ (node, False))
+ 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
+
+ def results_tsv(self):
+ 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)
+ self.write('\t'.join(
+ str(results.get(cset['node'], {}).get(mark, ('',))[0])
+ for mark in MARKS
+ ))
+ self.write('\n')
+ self.finish()
+
+ def results_asc(self):
+ self.set_header('Content-Type', 'text/plain; charset=UTF-8')
+ rev = self.get_argument('rev', 'tip')
+ revcount = 120
+ 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)
+ for match in matches:
+ msg = []
+ rows = self.conn.execute('SELECT mark, time FROM results WHERE node = ?', (match,))
+ for row in rows:
+ msg.append('{}: {:.2g}s'.format(*row))
+ line = line.replace(match, ' '.join(msg))
+ self.write(line)
+ self.finish()
+
+ 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):
+ def __init__(self):
+ handlers = [
+ URLSpec(r'/', IndexHandler),
+ URLSpec(r'/results\.(html|tsv|asc)', ResultsHandler),
+ ]
+ settings = dict(
+ template_path=TEMPLATES,
+ debug=options.debug
+ )
+ 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)
+
+
+def main():
+ options.parse_command_line()
+
+ application = Viewer()
+ application.listen(options.port, address=options.listen, xheaders=options.xheaders)
+
+ IOLoop.instance().start()
+
+
+if __name__ == '__main__':
+ main()