##// END OF EJS Templates
perf: allow profiling of more than one run...
perf: allow profiling of more than one run By default, we still profile the first run only. However profiling more run help to understand side effect from one run to the other. So we add an option to be able to do so.

File last commit:

r49730:6000f5b2 default
r52482:90ef3e04 default
Show More
revsetbenchmarks.py
388 lines | 10.2 KiB | text/x-python | PythonLexer
/ contrib / revsetbenchmarks.py
Gregory Szorc
global: use python3 in shebangs...
r46434 #!/usr/bin/env python3
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
# 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.
#
Pierre-Yves David
revsetbenchmarks: fix argument parsing...
r25535 # You should run this from the root of your mercurial repository.
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848 #
Pierre-Yves David
revsetbenchmarks: fix argument parsing...
r25535 # call with --help for details
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 import math
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 import optparse # cannot use argparse, python 2.7 only
Pierre-Yves David
revsetbenchmark: automatically finds the perf extension...
r21548 import os
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530 import re
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 import subprocess
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 import sys
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287
Augie Fackler
formatting: blacken the codebase...
r43346 DEFAULTVARIANTS = [
'plain',
'min',
'max',
'first',
'last',
'reverse',
'reverse+first',
'reverse+last',
'sort',
'sort+first',
'sort+last',
]
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540
Durham Goode
revsetbenchmark: remove python 2.7 dependency...
r20893 def check_output(*args, **kwargs):
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 kwargs.setdefault('stderr', subprocess.PIPE)
kwargs.setdefault('stdout', subprocess.PIPE)
proc = subprocess.Popen(*args, **kwargs)
Durham Goode
revsetbenchmark: remove python 2.7 dependency...
r20893 output, error = proc.communicate()
if proc.returncode != 0:
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0]))
Durham Goode
revsetbenchmark: remove python 2.7 dependency...
r20893 return output
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmark: convert update to proper subprocess call
r20850 def update(rev):
"""update the repo to a revision"""
try:
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)])
Augie Fackler
formatting: blacken the codebase...
r43346 check_output(
['make', 'local'], stderr=None
) # suppress output except for error/warning
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 except subprocess.CalledProcessError as exc:
Augie Fackler
formatting: blacken the codebase...
r43346 print('update to revision %s failed, aborting' % rev, file=sys.stderr)
Pierre-Yves David
revsetbenchmark: convert update to proper subprocess call
r20850 sys.exit(exc.returncode)
Pierre-Yves David
revsetbenchmarks: extract call to mercurial into a function...
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]
Augie Fackler
formatting: blacken the codebase...
r43346 fullcmd += [
'--config',
'extensions.perf=' + os.path.join(contribdir, 'perf.py'),
]
Pierre-Yves David
revsetbenchmarks: extract call to mercurial into a function...
r25528 fullcmd += cmd
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 return check_output(fullcmd, stderr=subprocess.STDOUT)
Pierre-Yves David
revsetbenchmarks: extract call to mercurial into a function...
r25528
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
revsetbenchmarks: support benchmarking changectx loading...
r27073 def perf(revset, target=None, contexts=False):
Pierre-Yves David
revsetbenchmark: convert performance call to proper subprocess call
r20851 """run benchmark for this very revset"""
try:
Boris Feld
revsetbenchmarks: support revset starting with a "-"...
r41308 args = ['perfrevset']
Gregory Szorc
revsetbenchmarks: support benchmarking changectx loading...
r27073 if contexts:
args.append('--contexts')
Boris Feld
revsetbenchmarks: support revset starting with a "-"...
r41308 args.append('--')
args.append(revset)
Gregory Szorc
revsetbenchmarks: support benchmarking changectx loading...
r27073 output = hg(args, repo=target)
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530 return parseoutput(output)
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 except subprocess.CalledProcessError as exc:
Augie Fackler
formatting: blacken the codebase...
r43346 print(
'abort: cannot run revset benchmark: %s' % exc.cmd, file=sys.stderr
)
if getattr(exc, 'output', None) is None: # no output before 2.7
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print('(no output)', file=sys.stderr)
Pierre-Yves David
revsetbenchmarks: improve error output in case of failure...
r25529 else:
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print(exc.output, file=sys.stderr)
Pierre-Yves David
revsetbenchmark: do not abort on failure to run a revset...
r25646 return None
Pierre-Yves David
revsetbenchmark: convert performance call to proper subprocess call
r20851
Augie Fackler
formatting: blacken the codebase...
r43346
outputre = re.compile(
br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
br'sys (\d+.\d+) \(best of (\d+)\)'
)
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530
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:
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print('abort: invalid output:', file=sys.stderr)
print(output, file=sys.stderr)
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530 sys.exit(1)
Augie Fackler
formatting: blacken the codebase...
r43346 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)),
}
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530
Pierre-Yves David
revsetbenchmark: convert revision display to proper subprocesscall
r20852 def printrevision(rev):
"""print data about a revision"""
Pierre-Yves David
revsetbenchmarks: improve revision printing...
r25538 sys.stdout.write("Revision ")
Pierre-Yves David
revsetbenchmark: convert revision display to proper subprocesscall
r20852 sys.stdout.flush()
Augie Fackler
formatting: blacken the codebase...
r43346 subprocess.check_call(
[
'hg',
'log',
'--rev',
str(rev),
'--template',
'{if(tags, " ({tags})")} ' '{rev}:{node|short}: {desc|firstline}\n',
]
)
Pierre-Yves David
revsetbenchmark: convert revision display to proper subprocesscall
r20852
Pierre-Yves David
revsetbenchmarks: ensure all indexes have the same width...
r25532 def idxwidth(nbidx):
"""return the max width of number used for index
Augie Fackler
revsetbenchmarks: clarify comment based on irc discussion
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.
"""
Augie Fackler
formatting: blacken the codebase...
r43346 nbidx -= 1 # starts at 0
Pierre-Yves David
revsetbenchmarks: ensure all indexes have the same width...
r25532 idxwidth = 0
while nbidx:
idxwidth += 1
nbidx //= 10
if not idxwidth:
idxwidth = 1
return idxwidth
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 def getfactor(main, other, field, sensitivity=0.05):
"""return the relative factor between values for 'field' in main and other
Mads Kiilerich
spelling: trivial spell checking
r26781 Return None if the factor is insignificant (less than <sensitivity>
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 variation)."""
factor = 1
if main is not None:
factor = other[field] / main[field]
low, high = 1 - sensitivity, 1 + sensitivity
Augie Fackler
formatting: blacken the codebase...
r43346 if low < factor < high:
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 return None
return factor
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 def formatfactor(factor):
"""format a factor into a 4 char string
22%
156%
x2.4
x23
x789
x1e4
x5x7
"""
if factor is None:
return ' '
elif factor < 2:
return '%3i%%' % (factor * 100)
elif factor < 10:
return 'x%3.1f' % factor
elif factor < 1000:
return '%4s' % ('x%i' % factor)
else:
order = int(math.log(factor)) + 1
Martin von Zweigbergk
cleanup: some Yoda conditions, this patch removes...
r40065 while math.log(factor) > 1:
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 factor //= 0
return 'x%ix%i' % (factor, order)
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 def formattiming(value):
"""format a value to strictly 8 char, dropping some precision if needed"""
Augie Fackler
formatting: blacken the codebase...
r43346 if value < 10 ** 7:
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 return ('%.6f' % value)[:8]
else:
# value is HUGE very unlikely to happen (4+ month run)
return '%i' % value
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 _marker = object()
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
Pierre-Yves David
revsetbenchmarks: factor out result output into a function...
r25531 """print a line of result to stdout"""
Pierre-Yves David
revsetbenchmarks: ensure all indexes have the same width...
r25532 mask = '%%0%ii) %%s' % idxwidth(maxidx)
Pierre-Yves David
revsetbenchmark: do not abort on failure to run a revset...
r25646
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 out = []
for var in variants:
Pierre-Yves David
revsetbenchmark: do not abort on failure to run a revset...
r25646 if data[var] is None:
out.append('error ')
out.append(' ' * 4)
continue
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 out.append(formattiming(data[var]['wall']))
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 if reference is not _marker:
factor = None
if reference is not None:
factor = getfactor(reference[var], data[var], 'wall')
out.append(formatfactor(factor))
if verbose:
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 out.append(formattiming(data[var]['comb']))
out.append(formattiming(data[var]['user']))
out.append(formattiming(data[var]['sys']))
Augie Fackler
formatting: blacken the codebase...
r43346 out.append('%6d' % data[var]['count'])
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print(mask % (idx, ' '.join(out)))
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 def printheader(variants, maxidx, verbose=False, relative=False):
header = [' ' * (idxwidth(maxidx) + 1)]
for var in variants:
if not var:
var = 'iter'
Martin von Zweigbergk
cleanup: some Yoda conditions, this patch removes...
r40065 if len(var) > 8:
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 var = var[:3] + '..' + var[-3:]
header.append('%-8s' % var)
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 if relative:
header.append(' ')
if verbose:
Pierre-Yves David
revsetbenchmarks: display even more compact timing result...
r25541 header.append('%-8s' % 'comb')
header.append('%-8s' % 'user')
header.append('%-8s' % 'sys')
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 header.append('%6s' % 'count')
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print(' '.join(header))
Pierre-Yves David
revsetbenchmarks: parse perfrevset output into actual number...
r25530
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
revsetbenchmark: get revision to benchmark in a function...
r20853 def getrevs(spec):
"""get the list of rev matched by a revset"""
try:
out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
Yuya Nishihara
py3: make contrib/revsetbenchmarks.py not import symbols from stdlib modules
r29210 except subprocess.CalledProcessError as exc:
Augie Fackler
formatting: blacken the codebase...
r43346 print("abort, can't get revision from %s" % spec, file=sys.stderr)
Pierre-Yves David
revsetbenchmark: get revision to benchmark in a function...
r20853 sys.exit(exc.returncode)
return [r for r in out.split() if r]
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 def applyvariants(revset, variant):
if variant == 'plain':
return revset
Pierre-Yves David
revsetbenchmarks: support combining variants with "+"...
r25543 for var in variant.split('+'):
revset = '%s(%s)' % (var, revset)
return revset
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540
Augie Fackler
formatting: blacken the codebase...
r43346
helptext = """This script will run multiple variants of provided revsets using
Pierre-Yves David
revsetbenchmarks: add main documention for the script...
r25607 different revisions in your mercurial repository. After the benchmark are run
Mads Kiilerich
spelling: trivial spell checking
r26781 summary output is provided. Use it to demonstrate speed improvements or pin
Pierre-Yves David
revsetbenchmarks: add main documention for the script...
r25607 point regressions. Revsets to run are specified in a file (or from stdin), one
revsets per line. Line starting with '#' will be ignored, allowing insertion of
comments."""
Augie Fackler
formatting: blacken the codebase...
r43346 parser = optparse.OptionParser(
usage="usage: %prog [options] <revs>", description=helptext
)
parser.add_option(
"-f",
"--file",
help="read revset from FILE (stdin if omitted)",
metavar="FILE",
)
parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO")
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287
Augie Fackler
formatting: blacken the codebase...
r43346 parser.add_option(
"-v",
"--verbose",
action='store_true',
help="display all timing data (not just best total time)",
)
Pierre-Yves David
revsetbenchmarks: hide most timing under a --verbose flag...
r25537
Augie Fackler
formatting: blacken the codebase...
r43346 parser.add_option(
"",
"--variants",
default=','.join(DEFAULTVARIANTS),
help="comma separated list of variant to test "
"(eg: plain,min,sorted) (plain = no modification)",
)
parser.add_option(
'',
'--contexts',
action='store_true',
help='obtain changectx from results instead of integer revs',
)
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287 (options, args) = parser.parse_args()
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pierre-Yves David
revsetbenchmarks: fix argument parsing...
r25535 if not args:
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287 parser.print_help()
Pierre-Yves David
revsetbenchmark: add a usage message when no arguments are passed...
r21286 sys.exit(255)
Pierre-Yves David
revsetbenchmark: automatically finds the perf extension...
r21548 # the directory where both this script and the perf.py extension live.
contribdir = os.path.dirname(__file__)
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848 revsetsfile = sys.stdin
Pierre-Yves David
revsetbenchmark: use optparse to retrieve argument...
r21287 if options.file:
revsetsfile = open(options.file)
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pierre-Yves David
revsetbenchmark: allow comments ('#' prefix) in the revset input
r22556 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
Pierre-Yves David
revsetbenchmarks: ignore empty lines...
r25642 revsets = [l for l in revsets if l]
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("Revsets to benchmark")
print("----------------------------")
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
for idx, rset in enumerate(revsets):
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("%i) %s" % (idx, rset))
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("----------------------------")
print()
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pierre-Yves David
revsetbenchmarks: fix argument parsing...
r25535 revs = []
for a in args:
revs.extend(getrevs(a))
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 variants = options.variants.split(',')
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855 results = []
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848 for r in revs:
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("----------------------------")
Pierre-Yves David
revsetbenchmark: convert revision display to proper subprocesscall
r20852 printrevision(r)
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("----------------------------")
Pierre-Yves David
revsetbenchmark: convert update to proper subprocess call
r20850 update(r)
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855 res = []
results.append(res)
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 printheader(variants, len(revsets), verbose=options.verbose)
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848 for idx, rset in enumerate(revsets):
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 varres = {}
for var in variants:
varrset = applyvariants(rset, var)
Gregory Szorc
revsetbenchmarks: support benchmarking changectx loading...
r27073 data = perf(varrset, target=options.repo, contexts=options.contexts)
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 varres[var] = data
res.append(varres)
Augie Fackler
formatting: blacken the codebase...
r43346 printresult(
variants, idx, varres, len(revsets), verbose=options.verbose
)
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855 sys.stdout.flush()
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("----------------------------")
Pierre-Yves David
revsetbenchmark: simplify and convert the script to python...
r20848
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855
Augie Fackler
formatting: blacken the codebase...
r43346 print(
"""
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855
Result by revset
================
Augie Fackler
formatting: blacken the codebase...
r43346 """
)
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print('Revision:')
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855 for idx, rev in enumerate(revs):
sys.stdout.write('%i) ' % idx)
sys.stdout.flush()
printrevision(rev)
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print()
print()
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855
for ridx, rset in enumerate(revsets):
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print("revset #%i: %s" % (ridx, rset))
Pierre-Yves David
revsetbenchmarks: allow running multiple variants per revset...
r25540 printheader(variants, len(results), verbose=options.verbose, relative=True)
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 ref = None
Pierre-Yves David
revsetbenchmark: add a summary at the end of execution...
r20855 for idx, data in enumerate(results):
Augie Fackler
formatting: blacken the codebase...
r43346 printresult(
variants,
idx,
data[ridx],
len(results),
verbose=options.verbose,
reference=ref,
)
Pierre-Yves David
revsetbenchmarks: display relative change when meaningful...
r25539 ref = data[ridx]
Pulkit Goyal
contrib: revsetbenchmarks use absolute_import and print_function
r28564 print()