Anton Shestakov <av6@dwimlabs.net>, Sun, 07 Aug 2016 13:03:34 +0800
bench: unlock at (any kind of) exit
Helps with running bench.py via a wrapper shell script, for example.
bench.py
Permissions: -rwxr-xr-x
from argparse import ArgumentParser from collections import OrderedDict from subprocess import check_output, CalledProcessError, STDOUT from settings import DBPATH, HG, LOCKFILE, TESTHGREPO, TESTREPO, rel REVSETWIP = '(parents(not public()) or not public() or . or (head() and branch(default))) and (not obsolete() or unstable()^) and not closed()' ('blame', 'hg blame README'), ('grepall', 'hg grep "version" --all README'), ('grepallf', 'hg grep "version" --all --follow README'), ('diff', 'hg diff -r "tip~100:tip" README'), ('diffg', 'hg diff -r "tip~100:tip" --git README'), ('stcp', 'hg status --copies README'), ('logfile', 'hg log README'), ('logfilecp', 'hg log --copies README'), ('log1000', 'hg log -l1000'), ('revsetor', 'hg log -r "0|1|2|3|4|5|6|7|8|9"'), ('revsetwip', 'hg log -r "' + REVSETWIP + '"'), parser = ArgumentParser(description='Benchmark revisions and put results in the db.') group = parser.add_mutually_exclusive_group() 'revsets', metavar='REVSET', nargs='*', default=('last(all(), 120)',), help='update these revisions (default: last 120)') '--auto', metavar='MAXREVS', type=int, help='guess revisions, up to MAXREVS') '--marks', metavar='MARKS', default='all', help='test only these commands (comma-separated, default: all)') '--retry', action='store_true', help='try and reduce existing timings') '--mintime', metavar='N', type=float, default=1.0, help='run each command for a total of at least N seconds (default: 1.0)') '--mintries', metavar='N', type=int, default=3, help='run each command at least N times (default: 3)') def test(mark, mintime, mintries, dropcache=True): cmd = [rel(TESTHGREPO, 'hg'), '-R', TESTREPO] filename = rel(TESTREPO, 'README') cmd += ['blame', filename] cmd += ['grep', '--all', 'version', filename] cmd += ['grep', '--all', '--follow', 'version', filename] cmd += ['status', '--copies', filename] cmd += ['diff', '-r', 'tip~100:tip', filename] cmd += ['diff', '-r', 'tip~100:tip', '--git', filename] elif mark == 'logfilecp': cmd += ['log', '--copies', filename] cmd += ['log', '-r', '0|1|2|3|4|5|6|7|8|9'] elif mark == 'revsetwip': cmd += ['log', '-r', REVSETWIP] while sum(results) < mintime or len(results) < mintries: shutil.rmtree(rel(TESTREPO, '.hg', 'cache'), ignore_errors=True) except CalledProcessError: results.append(time.time() - start) cmd = [HG, 'log', '-R', TESTHGREPO, '-T', '{node}\n'] output = check_output(cmd) def guessnew(maxrevs, marks): """ Pick one continuous span of nodes that still need testing. """ cmd = [HG, 'log', '-R', TESTHGREPO, '-T', '{node}\n', '-r', 'sort(all(), rev)'] output = check_output(cmd) conn = sqlite3.connect(DBPATH) while len(todo) < maxrevs: 'SELECT COUNT(*) FROM results' if count < len(marks) * len(('without cache', 'with cache')): def guessspikes(maxrevs, marks): cmd = [HG, 'log', '-R', TESTHGREPO, '-T', '{node}\n', '-r', 'sort(all(), -rev)'] output = check_output(cmd) conn = sqlite3.connect(DBPATH) 'SELECT MIN(time), MAX(time) FROM results' ' WHERE mark = ? AND cache = ?', (mark, False)).fetchone() 'SELECT MIN(time), MAX(time) FROM results' ' WHERE mark = ? AND cache = ?', 'SELECT mark, time, cache FROM results WHERE node = ?', for mark, t, cache in resultsq: results.setdefault(node, {}).setdefault(mark, [None, None]) results[node][mark][0] = t results[node][mark][1] = t for i in range(1, len(nodes) - 1): for cache in (False, True): eps = abs(results[node1][mark][cache] - results[node3][mark][cache]) delta = results[node2][mark][cache] - results[node1][mark][cache] l = limits[mark][2:4] if cache else limits[mark][0:2] if delta > eps * 10 and delta > (l[1] - l[0]) * 0.1: except (KeyError, TypeError): check_output(['make', '--directory', TESTHGREPO, 'clean'], stderr=STDOUT) check_output([HG, 'update', '-R', TESTHGREPO, '--clean', node], stderr=STDOUT) check_output(['make', '--directory', TESTHGREPO, 'local'], stderr=STDOUT) conn = sqlite3.connect(DBPATH) 'CREATE TABLE IF NOT EXISTS results (' ' node CHAR(40) NOT NULL,' ' mark VARCHAR(40) NOT NULL,' 'CREATE INDEX IF NOT EXISTS idx_results_node' 'CREATE INDEX IF NOT EXISTS idx_results_limits' ' ON results (mark, cache, node, time ASC)') def dbupdate(revsets, marks, mintime, mintries, retry=False): conn = sqlite3.connect(DBPATH) nodes = getnodes(revsets) numwidth = len(str(len(nodes))) markwidth = max(len(mark) for mark in marks) for i, node in enumerate(nodes, 1): for cache in (False, True): 'SELECT time FROM results' ' WHERE node = ? AND mark = ? AND cache = ?', (node, mark, cache)).fetchall() oldtime = old[0][0] if old else None if oldtime is not None and not retry: time = test(mark, mintime, mintries, dropcache=not cache) '%0*d/%0*d %s %-*s %s (%s)', numwidth, i, numwidth, len(nodes), node, markwidth, mark, time, status) 'INSERT INTO results (node, mark, time, cache)' (node, mark, time, cache)) 'UPDATE results SET time = ?' ' WHERE node = ? AND mark = ? AND cache = ?', (time, node, mark, cache)) fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL) os.write(fd, '%d' % os.getpid()) if e.errno == errno.EEXIST: logging.error('cannot lock data directory') if e.errno == errno.ENOENT: logging.info("lock file %r doesn't exist", path) names = args.marks.split(',') marks = OrderedDict((k, v) for k, v in MARKS.items() if k in names) atexit.register(unlock, lockfd, LOCKFILE) args.revsets = guessspikes(args.auto, marks) args.revsets = guessnew(args.auto, marks) dbupdate(args.revsets, marks, args.mintime, args.mintries, args.retry) if __name__ == '__main__': logging.basicConfig(format='%(levelname).1s %(asctime)s %(message)s', level=logging.INFO) args = parser.parse_args()