revsetbenchmarks.py
213 lines
| 6.0 KiB
| text/x-python
|
PythonLexer
/ contrib / revsetbenchmarks.py
|
r20848 | #!/usr/bin/env python | ||
# Measure the performance of a list of revsets against multiple revisions | ||||
# defined by parameter. Checkout one by one and run perfrevset with every | ||||
# revset in the list to benchmark its performance. | ||||
# | ||||
|
r25535 | # You should run this from the root of your mercurial repository. | ||
|
r20848 | # | ||
|
r25535 | # call with --help for details | ||
|
r20848 | # | ||
# This script also does one run of the current version of mercurial installed | ||||
# to compare performance. | ||||
import sys | ||||
|
r21548 | import os | ||
|
r25530 | import re | ||
|
r20893 | from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE | ||
|
r21287 | # cannot use argparse, python 2.7 only | ||
from optparse import OptionParser | ||||
|
r20893 | def check_output(*args, **kwargs): | ||
kwargs.setdefault('stderr', PIPE) | ||||
kwargs.setdefault('stdout', PIPE) | ||||
proc = Popen(*args, **kwargs) | ||||
output, error = proc.communicate() | ||||
if proc.returncode != 0: | ||||
|
r21202 | raise CalledProcessError(proc.returncode, ' '.join(args[0])) | ||
|
r20893 | return output | ||
|
r20848 | |||
|
r20850 | def update(rev): | ||
"""update the repo to a revision""" | ||||
try: | ||||
check_call(['hg', 'update', '--quiet', '--check', str(rev)]) | ||||
except CalledProcessError, exc: | ||||
print >> sys.stderr, 'update to revision %s failed, aborting' % rev | ||||
sys.exit(exc.returncode) | ||||
|
r25528 | |||
def hg(cmd, repo=None): | ||||
"""run a mercurial command | ||||
<cmd> is the list of command + argument, | ||||
<repo> is an optional repository path to run this command in.""" | ||||
fullcmd = ['./hg'] | ||||
if repo is not None: | ||||
fullcmd += ['-R', repo] | ||||
fullcmd += ['--config', | ||||
'extensions.perf=' + os.path.join(contribdir, 'perf.py')] | ||||
fullcmd += cmd | ||||
return check_output(fullcmd, stderr=STDOUT) | ||||
|
r21549 | def perf(revset, target=None): | ||
|
r20851 | """run benchmark for this very revset""" | ||
try: | ||||
|
r25528 | output = hg(['perfrevset', revset], repo=target) | ||
|
r25530 | return parseoutput(output) | ||
|
r20851 | except CalledProcessError, exc: | ||
|
r25529 | print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd | ||
if exc.output is None: | ||||
print >> sys.stderr, '(no ouput)' | ||||
else: | ||||
print >> sys.stderr, exc.output | ||||
|
r20851 | sys.exit(exc.returncode) | ||
|
r25530 | outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) ' | ||
'sys (\d+.\d+) \(best of (\d+)\)') | ||||
def parseoutput(output): | ||||
"""parse a textual output into a dict | ||||
We cannot just use json because we want to compare with old | ||||
versions of Mercurial that may not support json output. | ||||
""" | ||||
match = outputre.search(output) | ||||
if not match: | ||||
print >> sys.stderr, 'abort: invalid output:' | ||||
print >> sys.stderr, output | ||||
sys.exit(1) | ||||
return {'comb': float(match.group(2)), | ||||
'count': int(match.group(5)), | ||||
'sys': float(match.group(3)), | ||||
'user': float(match.group(4)), | ||||
'wall': float(match.group(1)), | ||||
} | ||||
|
r20852 | def printrevision(rev): | ||
"""print data about a revision""" | ||||
sys.stdout.write("Revision: ") | ||||
sys.stdout.flush() | ||||
check_call(['hg', 'log', '--rev', str(rev), '--template', | ||||
'{desc|firstline}\n']) | ||||
|
r25532 | def idxwidth(nbidx): | ||
"""return the max width of number used for index | ||||
|
r25533 | This is similar to log10(nbidx), but we use custom code here | ||
because we start with zero and we'd rather not deal with all the | ||||
extra rounding business that log10 would imply. | ||||
""" | ||||
|
r25532 | nbidx -= 1 # starts at 0 | ||
idxwidth = 0 | ||||
while nbidx: | ||||
idxwidth += 1 | ||||
nbidx //= 10 | ||||
if not idxwidth: | ||||
idxwidth = 1 | ||||
return idxwidth | ||||
|
r25531 | def printresult(idx, data, maxidx): | ||
"""print a line of result to stdout""" | ||||
|
r25532 | mask = '%%0%ii) %%s' % idxwidth(maxidx) | ||
|
r25534 | out = ['%10.6f' % data['wall'], | ||
'%10.6f' % data['comb'], | ||||
'%10.6f' % data['user'], | ||||
'%10.6f' % data['sys'], | ||||
'%6d' % data['count'], | ||||
] | ||||
print mask % (idx, ' '.join(out)) | ||||
|
r25530 | |||
|
r25534 | def printheader(maxidx): | ||
header = [' ' * (idxwidth(maxidx) + 1), | ||||
' %-8s' % 'wall', | ||||
' %-8s' % 'comb', | ||||
' %-8s' % 'user', | ||||
' %-8s' % 'sys', | ||||
'%6s' % 'count', | ||||
] | ||||
print ' '.join(header) | ||||
|
r25530 | |||
|
r20853 | def getrevs(spec): | ||
"""get the list of rev matched by a revset""" | ||||
try: | ||||
out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec]) | ||||
except CalledProcessError, exc: | ||||
print >> sys.stderr, "abort, can't get revision from %s" % spec | ||||
sys.exit(exc.returncode) | ||||
return [r for r in out.split() if r] | ||||
|
r21287 | parser = OptionParser(usage="usage: %prog [options] <revs>") | ||
parser.add_option("-f", "--file", | ||||
|
r23139 | help="read revset from FILE (stdin if omitted)", | ||
|
r22555 | metavar="FILE") | ||
|
r21549 | parser.add_option("-R", "--repo", | ||
help="run benchmark on REPO", metavar="REPO") | ||||
|
r21287 | |||
(options, args) = parser.parse_args() | ||||
|
r20848 | |||
|
r25535 | if not args: | ||
|
r21287 | parser.print_help() | ||
|
r21286 | sys.exit(255) | ||
|
r21548 | # the directory where both this script and the perf.py extension live. | ||
contribdir = os.path.dirname(__file__) | ||||
|
r21287 | |||
|
r20848 | revsetsfile = sys.stdin | ||
|
r21287 | if options.file: | ||
revsetsfile = open(options.file) | ||||
|
r20848 | |||
|
r22556 | revsets = [l.strip() for l in revsetsfile if not l.startswith('#')] | ||
|
r20848 | |||
print "Revsets to benchmark" | ||||
print "----------------------------" | ||||
for idx, rset in enumerate(revsets): | ||||
print "%i) %s" % (idx, rset) | ||||
print "----------------------------" | ||||
|
r25535 | revs = [] | ||
for a in args: | ||||
revs.extend(getrevs(a)) | ||||
|
r20848 | |||
|
r20855 | results = [] | ||
|
r20848 | for r in revs: | ||
print "----------------------------" | ||||
|
r20852 | printrevision(r) | ||
|
r20848 | print "----------------------------" | ||
|
r20850 | update(r) | ||
|
r20855 | res = [] | ||
results.append(res) | ||||
|
r25534 | printheader(len(revsets)) | ||
|
r20848 | for idx, rset in enumerate(revsets): | ||
|
r21549 | data = perf(rset, target=options.repo) | ||
|
r20855 | res.append(data) | ||
|
r25531 | printresult(idx, data, len(revsets)) | ||
|
r20855 | sys.stdout.flush() | ||
|
r20848 | print "----------------------------" | ||
|
r20855 | |||
print """ | ||||
Result by revset | ||||
================ | ||||
""" | ||||
print 'Revision:', revs | ||||
for idx, rev in enumerate(revs): | ||||
sys.stdout.write('%i) ' % idx) | ||||
sys.stdout.flush() | ||||
printrevision(rev) | ||||
for ridx, rset in enumerate(revsets): | ||||
print "revset #%i: %s" % (ridx, rset) | ||||
|
r25534 | printheader(len(results)) | ||
|
r20855 | for idx, data in enumerate(results): | ||
|
r25531 | printresult(idx, data[ridx], len(results)) | ||
|
r20855 | |||