perf.py
1103 lines
| 33.7 KiB
| text/x-python
|
PythonLexer
/ contrib / perf.py
|
r7366 | # perf.py - performance test routines | ||
|
r8873 | '''helper extension to measure performance''' | ||
|
r7366 | |||
|
r29493 | # "historical portability" policy of perf.py: | ||
# | ||||
# We have to do: | ||||
# - make perf.py "loadable" with as wide Mercurial version as possible | ||||
# This doesn't mean that perf commands work correctly with that Mercurial. | ||||
# BTW, perf.py itself has been available since 1.1 (or eb240755386d). | ||||
# - make historical perf command work correctly with as wide Mercurial | ||||
# version as possible | ||||
# | ||||
# We have to do, if possible with reasonable cost: | ||||
# - make recent perf command for historical feature work correctly | ||||
# with early Mercurial | ||||
# | ||||
# We don't have to do: | ||||
# - make perf command for recent feature work correctly with early | ||||
# Mercurial | ||||
|
r28561 | from __future__ import absolute_import | ||
import functools | ||||
import os | ||||
|
r27286 | import random | ||
|
r28561 | import sys | ||
import time | ||||
from mercurial import ( | ||||
|
r30018 | changegroup, | ||
|
r28561 | cmdutil, | ||
commands, | ||||
copies, | ||||
error, | ||||
|
r29495 | extensions, | ||
|
r28561 | mdiff, | ||
merge, | ||||
revlog, | ||||
util, | ||||
) | ||||
|
r7366 | |||
|
r29494 | # for "historical portability": | ||
|
r29567 | # try to import modules separately (in dict order), and ignore | ||
# failure, because these aren't available with early Mercurial | ||||
try: | ||||
from mercurial import branchmap # since 2.5 (or bcee63733aad) | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
from mercurial import obsolete # since 2.3 (or ad0d6c2b3279) | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
from mercurial import repoview # since 2.5 (or 3a6ddacb7198) | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
from mercurial import scmutil # since 1.9 (or 8b252e826c68) | ||||
except ImportError: | ||||
pass | ||||
# for "historical portability": | ||||
|
r29494 | # define util.safehasattr forcibly, because util.safehasattr has been | ||
# available since 1.9.3 (or 94b200a11cf7) | ||||
_undefined = object() | ||||
def safehasattr(thing, attr): | ||||
return getattr(thing, attr, _undefined) is not _undefined | ||||
setattr(util, 'safehasattr', safehasattr) | ||||
|
r29496 | # for "historical portability": | ||
# use locally defined empty option list, if formatteropts isn't | ||||
# available, because commands.formatteropts has been available since | ||||
# 3.2 (or 7a7eed5176a4), even though formatting itself has been | ||||
# available since 2.2 (or ae5f92e154d3) | ||||
formatteropts = getattr(commands, "formatteropts", []) | ||||
|
r29495 | |||
# for "historical portability": | ||||
# use locally defined option list, if debugrevlogopts isn't available, | ||||
# because commands.debugrevlogopts has been available since 3.7 (or | ||||
# 5606f7d0d063), even though cmdutil.openrevlog() has been available | ||||
# since 1.9 (or a79fea6b3e77). | ||||
revlogopts = getattr(commands, "debugrevlogopts", [ | ||||
('c', 'changelog', False, ('open changelog')), | ||||
('m', 'manifest', False, ('open manifest')), | ||||
('', 'dir', False, ('open directory manifest')), | ||||
]) | ||||
|
r25494 | |||
|
r18237 | cmdtable = {} | ||
|
r29497 | |||
# for "historical portability": | ||||
# define parsealiases locally, because cmdutil.parsealiases has been | ||||
# available since 1.5 (or 6252852b4332) | ||||
def parsealiases(cmd): | ||||
return cmd.lstrip("^").split("|") | ||||
if safehasattr(cmdutil, 'command'): | ||||
import inspect | ||||
command = cmdutil.command(cmdtable) | ||||
if 'norepo' not in inspect.getargspec(command)[0]: | ||||
# for "historical portability": | ||||
# wrap original cmdutil.command, because "norepo" option has | ||||
# been available since 3.1 (or 75a96326cecb) | ||||
_command = command | ||||
def command(name, options=(), synopsis=None, norepo=False): | ||||
if norepo: | ||||
commands.norepo += ' %s' % ' '.join(parsealiases(name)) | ||||
return _command(name, list(options), synopsis) | ||||
else: | ||||
# for "historical portability": | ||||
# define "@command" annotation locally, because cmdutil.command | ||||
# has been available since 1.9 (or 2daa5179e73f) | ||||
def command(name, options=(), synopsis=None, norepo=False): | ||||
def decorator(func): | ||||
if synopsis: | ||||
cmdtable[name] = func, list(options), synopsis | ||||
else: | ||||
cmdtable[name] = func, list(options) | ||||
if norepo: | ||||
commands.norepo += ' %s' % ' '.join(parsealiases(name)) | ||||
return func | ||||
return decorator | ||||
|
r18237 | |||
|
r27307 | def getlen(ui): | ||
if ui.configbool("perf", "stub"): | ||||
return lambda x: 1 | ||||
return len | ||||
|
r23171 | def gettimer(ui, opts=None): | ||
"""return a timer function and formatter: (timer, formatter) | ||||
|
r27303 | This function exists to gather the creation of formatter in a single | ||
place instead of duplicating it in all performance commands.""" | ||||
|
r23788 | |||
# enforce an idle period before execution to counteract power management | ||||
|
r25850 | # experimental config: perf.presleep | ||
|
r30149 | time.sleep(getint(ui, "perf", "presleep", 1)) | ||
|
r23788 | |||
|
r23171 | if opts is None: | ||
opts = {} | ||||
# redirect all to stderr | ||||
ui = ui.copy() | ||||
|
r30148 | uifout = safeattrsetter(ui, 'fout', ignoremissing=True) | ||
if uifout: | ||||
# for "historical portability": | ||||
# ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d) | ||||
uifout.set(ui.ferr) | ||||
|
r30147 | |||
|
r23171 | # get a formatter | ||
|
r30147 | uiformatter = getattr(ui, 'formatter', None) | ||
if uiformatter: | ||||
fm = uiformatter('perf', opts) | ||||
else: | ||||
# for "historical portability": | ||||
# define formatter locally, because ui.formatter has been | ||||
# available since 2.2 (or ae5f92e154d3) | ||||
from mercurial import node | ||||
class defaultformatter(object): | ||||
"""Minimized composition of baseformatter and plainformatter | ||||
""" | ||||
def __init__(self, ui, topic, opts): | ||||
self._ui = ui | ||||
if ui.debugflag: | ||||
self.hexfunc = node.hex | ||||
else: | ||||
self.hexfunc = node.short | ||||
def __nonzero__(self): | ||||
return False | ||||
def startitem(self): | ||||
pass | ||||
def data(self, **data): | ||||
pass | ||||
def write(self, fields, deftext, *fielddata, **opts): | ||||
self._ui.write(deftext % fielddata, **opts) | ||||
def condwrite(self, cond, fields, deftext, *fielddata, **opts): | ||||
if cond: | ||||
self._ui.write(deftext % fielddata, **opts) | ||||
def plain(self, text, **opts): | ||||
self._ui.write(text, **opts) | ||||
def end(self): | ||||
pass | ||||
fm = defaultformatter(ui, 'perf', opts) | ||||
|
r27304 | # stub function, runs code only once instead of in a loop | ||
# experimental config: perf.stub | ||||
if ui.configbool("perf", "stub"): | ||||
return functools.partial(stub_timer, fm), fm | ||||
|
r23171 | return functools.partial(_timer, fm), fm | ||
|
r27304 | def stub_timer(fm, func, title=None): | ||
func() | ||||
|
r23171 | def _timer(fm, func, title=None): | ||
|
r7366 | results = [] | ||
begin = time.time() | ||||
count = 0 | ||||
|
r14494 | while True: | ||
|
r7366 | ostart = os.times() | ||
cstart = time.time() | ||||
r = func() | ||||
cstop = time.time() | ||||
ostop = os.times() | ||||
count += 1 | ||||
a, b = ostart, ostop | ||||
results.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) | ||||
if cstop - begin > 3 and count >= 100: | ||||
break | ||||
if cstop - begin > 10 and count >= 3: | ||||
break | ||||
|
r23171 | |||
fm.startitem() | ||||
|
r9826 | if title: | ||
|
r23171 | fm.write('title', '! %s\n', title) | ||
|
r7366 | if r: | ||
|
r23171 | fm.write('result', '! result: %s\n', r) | ||
|
r7366 | m = min(results) | ||
|
r23171 | fm.plain('!') | ||
fm.write('wall', ' wall %f', m[0]) | ||||
fm.write('comb', ' comb %f', m[1] + m[2]) | ||||
fm.write('user', ' user %f', m[1]) | ||||
fm.write('sys', ' sys %f', m[2]) | ||||
fm.write('count', ' (best of %d)', count) | ||||
fm.plain('\n') | ||||
|
r7366 | |||
|
r30143 | # utilities for historical portability | ||
|
r30149 | def getint(ui, section, name, default): | ||
# for "historical portability": | ||||
# ui.configint has been available since 1.9 (or fa2b596db182) | ||||
v = ui.config(section, name, None) | ||||
if v is None: | ||||
return default | ||||
try: | ||||
return int(v) | ||||
except ValueError: | ||||
raise error.ConfigError(("%s.%s is not an integer ('%s')") | ||||
% (section, name, v)) | ||||
|
r30143 | def safeattrsetter(obj, name, ignoremissing=False): | ||
"""Ensure that 'obj' has 'name' attribute before subsequent setattr | ||||
This function is aborted, if 'obj' doesn't have 'name' attribute | ||||
at runtime. This avoids overlooking removal of an attribute, which | ||||
breaks assumption of performance measurement, in the future. | ||||
This function returns the object to (1) assign a new value, and | ||||
(2) restore an original value to the attribute. | ||||
If 'ignoremissing' is true, missing 'name' attribute doesn't cause | ||||
abortion, and this function returns None. This is useful to | ||||
examine an attribute, which isn't ensured in all Mercurial | ||||
versions. | ||||
""" | ||||
if not util.safehasattr(obj, name): | ||||
if ignoremissing: | ||||
return None | ||||
raise error.Abort(("missing attribute %s of %s might break assumption" | ||||
" of performance measurement") % (name, obj)) | ||||
origvalue = getattr(obj, name) | ||||
class attrutil(object): | ||||
def set(self, newvalue): | ||||
setattr(obj, name, newvalue) | ||||
def restore(self): | ||||
setattr(obj, name, origvalue) | ||||
return attrutil() | ||||
|
r30144 | # utilities to examine each internal API changes | ||
def getbranchmapsubsettable(): | ||||
# for "historical portability": | ||||
# subsettable is defined in: | ||||
# - branchmap since 2.9 (or 175c6fd8cacc) | ||||
# - repoview since 2.5 (or 59a9f18d4587) | ||||
for mod in (branchmap, repoview): | ||||
subsettable = getattr(mod, 'subsettable', None) | ||||
if subsettable: | ||||
return subsettable | ||||
# bisecting in bcee63733aad::59a9f18d4587 can reach here (both | ||||
# branchmap and repoview modules exist, but subsettable attribute | ||||
# doesn't) | ||||
raise error.Abort(("perfbranchmap not available with this Mercurial"), | ||||
hint="use 2.5 or later") | ||||
|
r30146 | def getsvfs(repo): | ||
"""Return appropriate object to access files under .hg/store | ||||
""" | ||||
# for "historical portability": | ||||
# repo.svfs has been available since 2.3 (or 7034365089bf) | ||||
svfs = getattr(repo, 'svfs', None) | ||||
if svfs: | ||||
return svfs | ||||
else: | ||||
return getattr(repo, 'sopener') | ||||
def getvfs(repo): | ||||
"""Return appropriate object to access files under .hg | ||||
""" | ||||
# for "historical portability": | ||||
# repo.vfs has been available since 2.3 (or 7034365089bf) | ||||
vfs = getattr(repo, 'vfs', None) | ||||
if vfs: | ||||
return vfs | ||||
else: | ||||
return getattr(repo, 'opener') | ||||
|
r30150 | def repocleartagscachefunc(repo): | ||
"""Return the function to clear tags cache according to repo internal API | ||||
""" | ||||
if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525) | ||||
# in this case, setattr(repo, '_tagscache', None) or so isn't | ||||
# correct way to clear tags cache, because existing code paths | ||||
# expect _tagscache to be a structured object. | ||||
def clearcache(): | ||||
# _tagscache has been filteredpropertycache since 2.5 (or | ||||
# 98c867ac1330), and delattr() can't work in such case | ||||
if '_tagscache' in vars(repo): | ||||
del repo.__dict__['_tagscache'] | ||||
return clearcache | ||||
repotags = safeattrsetter(repo, '_tags', ignoremissing=True) | ||||
if repotags: # since 1.4 (or 5614a628d173) | ||||
return lambda : repotags.set(None) | ||||
repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True) | ||||
if repotagscache: # since 0.6 (or d7df759d0e97) | ||||
return lambda : repotagscache.set(None) | ||||
# Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches | ||||
# this point, but it isn't so problematic, because: | ||||
# - repo.tags of such Mercurial isn't "callable", and repo.tags() | ||||
# in perftags() causes failure soon | ||||
# - perf.py itself has been available since 1.1 (or eb240755386d) | ||||
raise error.Abort(("tags API of this hg command is unknown")) | ||||
|
r30143 | # perf commands | ||
|
r25494 | @command('perfwalk', formatteropts) | ||
def perfwalk(ui, repo, *pats, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r7366 | try: | ||
|
r14671 | m = scmutil.match(repo[None], pats, {}) | ||
|
r10176 | timer(lambda: len(list(repo.dirstate.walk(m, [], True, False)))) | ||
|
r16689 | except Exception: | ||
|
r7366 | try: | ||
|
r14671 | m = scmutil.match(repo[None], pats, {}) | ||
|
r10282 | timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)])) | ||
|
r16689 | except Exception: | ||
|
r7366 | timer(lambda: len(list(cmdutil.walk(repo, pats, {})))) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfannotate', formatteropts) | ||
def perfannotate(ui, repo, f, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r19292 | fc = repo['.'][f] | ||
timer(lambda: len(fc.annotate(True))) | ||||
|
r23171 | fm.end() | ||
|
r19292 | |||
|
r18237 | @command('perfstatus', | ||
[('u', 'unknown', False, | ||||
|
r25494 | 'ask status to look for unknown files')] + formatteropts) | ||
|
r18033 | def perfstatus(ui, repo, **opts): | ||
|
r7366 | #m = match.always(repo.root, repo.getcwd()) | ||
|
r16683 | #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False, | ||
# False)))) | ||||
|
r27017 | timer, fm = gettimer(ui, opts) | ||
|
r25494 | timer(lambda: sum(map(len, repo.status(unknown=opts['unknown'])))) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfaddremove', formatteropts) | ||
def perfaddremove(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r18871 | try: | ||
oldquiet = repo.ui.quiet | ||||
repo.ui.quiet = True | ||||
|
r23533 | matcher = scmutil.match(repo[None]) | ||
|
r23537 | timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True)) | ||
|
r18871 | finally: | ||
repo.ui.quiet = oldquiet | ||||
|
r23171 | fm.end() | ||
|
r18871 | |||
|
r16785 | def clearcaches(cl): | ||
# behave somewhat consistently across internal API changes | ||||
if util.safehasattr(cl, 'clearcaches'): | ||||
cl.clearcaches() | ||||
elif util.safehasattr(cl, '_nodecache'): | ||||
from mercurial.node import nullid, nullrev | ||||
cl._nodecache = {nullid: nullrev} | ||||
cl._nodepos = None | ||||
|
r25494 | @command('perfheads', formatteropts) | ||
def perfheads(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16785 | cl = repo.changelog | ||
def d(): | ||||
len(cl.headrevs()) | ||||
clearcaches(cl) | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perftags', formatteropts) | ||
def perftags(ui, repo, **opts): | ||||
|
r19786 | import mercurial.changelog | ||
import mercurial.manifest | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r30146 | svfs = getsvfs(repo) | ||
|
r30150 | repocleartagscache = repocleartagscachefunc(repo) | ||
|
r7366 | def t(): | ||
|
r30146 | repo.changelog = mercurial.changelog.changelog(svfs) | ||
|
r30219 | repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo) | ||
|
r30150 | repocleartagscache() | ||
|
r7366 | return len(repo.tags()) | ||
timer(t) | ||||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfancestors', formatteropts) | ||
def perfancestors(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16802 | heads = repo.changelog.headrevs() | ||
def d(): | ||||
|
r16866 | for a in repo.changelog.ancestors(heads): | ||
|
r16802 | pass | ||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r16802 | |||
|
r25494 | @command('perfancestorset', formatteropts) | ||
def perfancestorset(ui, repo, revset, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r18080 | revs = repo.revs(revset) | ||
heads = repo.changelog.headrevs() | ||||
def d(): | ||||
|
r18091 | s = repo.changelog.ancestors(heads) | ||
|
r18080 | for rev in revs: | ||
rev in s | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r18080 | |||
|
r30018 | @command('perfchangegroupchangelog', formatteropts + | ||
[('', 'version', '02', 'changegroup version'), | ||||
('r', 'rev', '', 'revisions to add to changegroup')]) | ||||
def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts): | ||||
"""Benchmark producing a changelog group for a changegroup. | ||||
This measures the time spent processing the changelog during a | ||||
bundle operation. This occurs during `hg bundle` and on a server | ||||
processing a `getbundle` wire protocol request (handles clones | ||||
and pull requests). | ||||
By default, all revisions are added to the changegroup. | ||||
""" | ||||
cl = repo.changelog | ||||
revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')] | ||||
bundler = changegroup.getbundler(version, repo) | ||||
def lookup(node): | ||||
# The real bundler reads the revision in order to access the | ||||
# manifest node and files list. Do that here. | ||||
cl.read(node) | ||||
return node | ||||
def d(): | ||||
for chunk in bundler.group(revs, cl, lookup): | ||||
pass | ||||
timer, fm = gettimer(ui, opts) | ||||
timer(d) | ||||
fm.end() | ||||
|
r25494 | @command('perfdirs', formatteropts) | ||
def perfdirs(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r18845 | dirstate = repo.dirstate | ||
'a' in dirstate | ||||
def d(): | ||||
dirstate.dirs() | ||||
del dirstate._dirs | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r18845 | |||
|
r25494 | @command('perfdirstate', formatteropts) | ||
def perfdirstate(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r7366 | "a" in repo.dirstate | ||
def d(): | ||||
repo.dirstate.invalidate() | ||||
"a" in repo.dirstate | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfdirstatedirs', formatteropts) | ||
def perfdirstatedirs(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r7366 | "a" in repo.dirstate | ||
def d(): | ||||
"a" in repo.dirstate._dirs | ||||
del repo.dirstate._dirs | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfdirstatefoldmap', formatteropts) | ||
|
r27095 | def perfdirstatefoldmap(ui, repo, **opts): | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r22780 | dirstate = repo.dirstate | ||
'a' in dirstate | ||||
def d(): | ||||
|
r24607 | dirstate._filefoldmap.get('a') | ||
del dirstate._filefoldmap | ||||
timer(d) | ||||
fm.end() | ||||
|
r25494 | @command('perfdirfoldmap', formatteropts) | ||
def perfdirfoldmap(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r24607 | dirstate = repo.dirstate | ||
'a' in dirstate | ||||
def d(): | ||||
dirstate._dirfoldmap.get('a') | ||||
del dirstate._dirfoldmap | ||||
|
r22780 | del dirstate._dirs | ||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r22780 | |||
|
r25494 | @command('perfdirstatewrite', formatteropts) | ||
def perfdirstatewrite(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16788 | ds = repo.dirstate | ||
"a" in ds | ||||
def d(): | ||||
ds._dirty = True | ||||
|
r26748 | ds.write(repo.currenttransaction()) | ||
|
r16788 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r16788 | |||
|
r18817 | @command('perfmergecalculate', | ||
|
r25494 | [('r', 'rev', '.', 'rev to merge against')] + formatteropts) | ||
def perfmergecalculate(ui, repo, rev, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r18817 | wctx = repo[None] | ||
rctx = scmutil.revsingle(repo, rev, rev) | ||||
ancestor = wctx.ancestor(rctx) | ||||
# we don't want working dir files to be stat'd in the benchmark, so prime | ||||
# that cache | ||||
wctx.dirty() | ||||
def d(): | ||||
# acceptremote is True because we don't want prompts in the middle of | ||||
# our benchmark | ||||
|
r27098 | merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False, | ||
|
r27345 | acceptremote=True, followcopies=True) | ||
|
r18817 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r18817 | |||
|
r18877 | @command('perfpathcopies', [], "REV REV") | ||
|
r25494 | def perfpathcopies(ui, repo, rev1, rev2, **opts): | ||
timer, fm = gettimer(ui, opts) | ||||
|
r18877 | ctx1 = scmutil.revsingle(repo, rev1, rev1) | ||
ctx2 = scmutil.revsingle(repo, rev2, rev2) | ||||
def d(): | ||||
copies.pathcopies(ctx1, ctx2) | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r18877 | |||
|
r19712 | @command('perfmanifest', [], 'REV') | ||
|
r25494 | def perfmanifest(ui, repo, rev, **opts): | ||
timer, fm = gettimer(ui, opts) | ||||
|
r19712 | ctx = scmutil.revsingle(repo, rev, rev) | ||
t = ctx.manifestnode() | ||||
|
r7366 | def d(): | ||
|
r27467 | repo.manifest.clearcaches() | ||
|
r19378 | repo.manifest.read(t) | ||
|
r7366 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfchangeset', formatteropts) | ||
def perfchangeset(ui, repo, rev, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16262 | n = repo[rev].node() | ||
def d(): | ||||
|
r19378 | repo.changelog.read(n) | ||
|
r16266 | #repo.changelog._cache = None | ||
|
r16262 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r16262 | |||
|
r25494 | @command('perfindex', formatteropts) | ||
def perfindex(ui, repo, **opts): | ||||
|
r13255 | import mercurial.revlog | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r13277 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg | ||
|
r13254 | n = repo["tip"].node() | ||
|
r30146 | svfs = getsvfs(repo) | ||
|
r7366 | def d(): | ||
|
r30146 | cl = mercurial.revlog.revlog(svfs, "00changelog.i") | ||
|
r16260 | cl.rev(n) | ||
|
r7366 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfstartup', formatteropts) | ||
def perfstartup(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r7366 | cmd = sys.argv[0] | ||
def d(): | ||||
|
r27382 | if os.name != 'nt': | ||
os.system("HGRCPATH= %s version -q > /dev/null" % cmd) | ||||
else: | ||||
os.environ['HGRCPATH'] = '' | ||||
os.system("%s version -q > NUL" % cmd) | ||||
|
r7366 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfparents', formatteropts) | ||
def perfparents(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r27305 | # control the number of commits perfparents iterates over | ||
# experimental config: perf.parentscount | ||||
|
r30149 | count = getint(ui, "perf", "parentscount", 1000) | ||
|
r27305 | if len(repo.changelog) < count: | ||
raise error.Abort("repo needs %d commits for this test" % count) | ||||
|
r27100 | repo = repo.unfiltered() | ||
|
r27305 | nl = [repo.changelog.node(i) for i in xrange(count)] | ||
|
r7366 | def d(): | ||
for n in nl: | ||||
repo.changelog.parents(n) | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfctxfiles', formatteropts) | ||
|
r27095 | def perfctxfiles(ui, repo, x, **opts): | ||
|
r24349 | x = int(x) | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r24349 | def d(): | ||
len(repo[x].files()) | ||||
timer(d) | ||||
fm.end() | ||||
|
r25494 | @command('perfrawfiles', formatteropts) | ||
|
r27095 | def perfrawfiles(ui, repo, x, **opts): | ||
|
r24349 | x = int(x) | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r24349 | cl = repo.changelog | ||
def d(): | ||||
len(cl.read(x)[3]) | ||||
timer(d) | ||||
fm.end() | ||||
|
r25494 | @command('perflookup', formatteropts) | ||
def perflookup(ui, repo, rev, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r7366 | timer(lambda: len(repo.lookup(rev))) | ||
|
r23171 | fm.end() | ||
|
r7366 | |||
|
r25494 | @command('perfrevrange', formatteropts) | ||
def perfrevrange(ui, repo, *specs, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16858 | revrange = scmutil.revrange | ||
timer(lambda: len(revrange(repo, specs))) | ||||
|
r23171 | fm.end() | ||
|
r16858 | |||
|
r25494 | @command('perfnodelookup', formatteropts) | ||
def perfnodelookup(ui, repo, rev, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r16309 | import mercurial.revlog | ||
mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg | ||||
n = repo[rev].node() | ||||
|
r30146 | cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i") | ||
|
r16414 | def d(): | ||
cl.rev(n) | ||||
|
r16785 | clearcaches(cl) | ||
|
r16414 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r16414 | |||
|
r18237 | @command('perflog', | ||
|
r25494 | [('', 'rename', False, 'ask log to follow renames')] + formatteropts) | ||
|
r27306 | def perflog(ui, repo, rev=None, **opts): | ||
if rev is None: | ||||
rev=[] | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r7872 | ui.pushbuffer() | ||
|
r27306 | timer(lambda: commands.log(ui, repo, rev=rev, date='', user='', | ||
|
r9932 | copies=opts.get('rename'))) | ||
|
r7872 | ui.popbuffer() | ||
|
r23171 | fm.end() | ||
|
r7872 | |||
|
r25494 | @command('perfmoonwalk', formatteropts) | ||
def perfmoonwalk(ui, repo, **opts): | ||||
|
r20178 | """benchmark walking the changelog backwards | ||
This also loads the changelog data for each revision in the changelog. | ||||
""" | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r20178 | def moonwalk(): | ||
for i in xrange(len(repo), -1, -1): | ||||
ctx = repo[i] | ||||
ctx.branch() # read changelog data (in addition to the index) | ||||
timer(moonwalk) | ||||
|
r23171 | fm.end() | ||
|
r20178 | |||
|
r25494 | @command('perftemplating', formatteropts) | ||
|
r27306 | def perftemplating(ui, repo, rev=None, **opts): | ||
if rev is None: | ||||
rev=[] | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r7872 | ui.pushbuffer() | ||
|
r27306 | timer(lambda: commands.log(ui, repo, rev=rev, date='', user='', | ||
|
r7872 | template='{date|shortdate} [{rev}:{node|short}]' | ||
' {author|person}: {desc|firstline}\n')) | ||||
ui.popbuffer() | ||||
|
r23171 | fm.end() | ||
|
r7872 | |||
|
r25494 | @command('perfcca', formatteropts) | ||
def perfcca(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r17216 | timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate)) | ||
|
r23171 | fm.end() | ||
|
r16386 | |||
|
r25494 | @command('perffncacheload', formatteropts) | ||
def perffncacheload(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r17780 | s = repo.store | ||
|
r16403 | def d(): | ||
s.fncache._load() | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r16403 | |||
|
r25494 | @command('perffncachewrite', formatteropts) | ||
def perffncachewrite(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r17780 | s = repo.store | ||
|
r16403 | s.fncache._load() | ||
|
r27097 | lock = repo.lock() | ||
tr = repo.transaction('perffncachewrite') | ||||
|
r16403 | def d(): | ||
s.fncache._dirty = True | ||||
|
r27097 | s.fncache.write(tr) | ||
|
r16403 | timer(d) | ||
|
r30069 | tr.close() | ||
|
r27097 | lock.release() | ||
|
r23171 | fm.end() | ||
|
r16403 | |||
|
r25494 | @command('perffncacheencode', formatteropts) | ||
def perffncacheencode(ui, repo, **opts): | ||||
timer, fm = gettimer(ui, opts) | ||||
|
r17780 | s = repo.store | ||
|
r17553 | s.fncache._load() | ||
def d(): | ||||
for p in s.fncache.entries: | ||||
s.encode(p) | ||||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r17553 | |||
|
r25494 | @command('perfdiffwd', formatteropts) | ||
def perfdiffwd(ui, repo, **opts): | ||||
|
r9826 | """Profile diff of working directory changes""" | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r9826 | options = { | ||
'w': 'ignore_all_space', | ||||
'b': 'ignore_space_change', | ||||
'B': 'ignore_blank_lines', | ||||
} | ||||
for diffopt in ('', 'w', 'b', 'B', 'wB'): | ||||
opts = dict((options[c], '1') for c in diffopt) | ||||
def d(): | ||||
ui.pushbuffer() | ||||
commands.diff(ui, repo, **opts) | ||||
ui.popbuffer() | ||||
title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none') | ||||
timer(d, title) | ||||
|
r23171 | fm.end() | ||
|
r9826 | |||
|
r27492 | @command('perfrevlog', revlogopts + formatteropts + | ||
|
r27493 | [('d', 'dist', 100, 'distance between the revisions'), | ||
|
r30017 | ('s', 'startrev', 0, 'revision to start reading at'), | ||
('', 'reverse', False, 'read in reverse')], | ||||
|
r27492 | '-c|-m|FILE') | ||
|
r30017 | def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts): | ||
|
r27492 | """Benchmark reading a series of revisions from a revlog. | ||
By default, we read every ``-d/--dist`` revision from 0 to tip of | ||||
the specified revlog. | ||||
|
r27493 | |||
The start revision can be defined via ``-s/--startrev``. | ||||
|
r27492 | """ | ||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r27308 | _len = getlen(ui) | ||
|
r30017 | |||
|
r11694 | def d(): | ||
|
r27492 | r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts) | ||
|
r30017 | |||
startrev = 0 | ||||
endrev = _len(r) | ||||
dist = opts['dist'] | ||||
if reverse: | ||||
startrev, endrev = endrev, startrev | ||||
dist = -1 * dist | ||||
for x in xrange(startrev, endrev, dist): | ||||
|
r11694 | r.revision(r.node(x)) | ||
timer(d) | ||||
|
r23171 | fm.end() | ||
|
r11694 | |||
|
r27470 | @command('perfrevlogrevision', revlogopts + formatteropts + | ||
[('', 'cache', False, 'use caches instead of clearing')], | ||||
'-c|-m|FILE REV') | ||||
def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts): | ||||
"""Benchmark obtaining a revlog revision. | ||||
Obtaining a revlog revision consists of roughly the following steps: | ||||
1. Compute the delta chain | ||||
2. Obtain the raw chunks for that delta chain | ||||
3. Decompress each raw chunk | ||||
4. Apply binary patches to obtain fulltext | ||||
5. Verify hash of fulltext | ||||
This command measures the time spent in each of these phases. | ||||
""" | ||||
if opts.get('changelog') or opts.get('manifest'): | ||||
file_, rev = None, file_ | ||||
elif rev is None: | ||||
raise error.CommandError('perfrevlogrevision', 'invalid arguments') | ||||
r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts) | ||||
node = r.lookup(rev) | ||||
rev = r.rev(node) | ||||
def dodeltachain(rev): | ||||
if not cache: | ||||
r.clearcaches() | ||||
r._deltachain(rev) | ||||
def doread(chain): | ||||
if not cache: | ||||
r.clearcaches() | ||||
r._chunkraw(chain[0], chain[-1]) | ||||
def dodecompress(data, chain): | ||||
if not cache: | ||||
r.clearcaches() | ||||
start = r.start | ||||
length = r.length | ||||
inline = r._inline | ||||
iosize = r._io.size | ||||
buffer = util.buffer | ||||
offset = start(chain[0]) | ||||
for rev in chain: | ||||
chunkstart = start(rev) | ||||
if inline: | ||||
chunkstart += (rev + 1) * iosize | ||||
chunklength = length(rev) | ||||
b = buffer(data, chunkstart - offset, chunklength) | ||||
revlog.decompress(b) | ||||
def dopatch(text, bins): | ||||
if not cache: | ||||
r.clearcaches() | ||||
mdiff.patches(text, bins) | ||||
def dohash(text): | ||||
if not cache: | ||||
r.clearcaches() | ||||
r._checkhash(text, node, rev) | ||||
def dorevision(): | ||||
if not cache: | ||||
r.clearcaches() | ||||
r.revision(node) | ||||
chain = r._deltachain(rev)[0] | ||||
|
r27649 | data = r._chunkraw(chain[0], chain[-1])[1] | ||
|
r27470 | bins = r._chunks(chain) | ||
text = str(bins[0]) | ||||
bins = bins[1:] | ||||
text = mdiff.patches(text, bins) | ||||
benches = [ | ||||
(lambda: dorevision(), 'full'), | ||||
(lambda: dodeltachain(rev), 'deltachain'), | ||||
(lambda: doread(chain), 'read'), | ||||
(lambda: dodecompress(data, chain), 'decompress'), | ||||
(lambda: dopatch(text, bins), 'patch'), | ||||
(lambda: dohash(text), 'hash'), | ||||
] | ||||
for fn, title in benches: | ||||
timer, fm = gettimer(ui, opts) | ||||
timer(fn, title=title) | ||||
fm.end() | ||||
|
r18239 | @command('perfrevset', | ||
|
r27072 | [('C', 'clear', False, 'clear volatile cache between each call.'), | ||
('', 'contexts', False, 'obtain changectx for each revision')] | ||||
|
r25494 | + formatteropts, "REVSET") | ||
|
r27072 | def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts): | ||
|
r18239 | """benchmark the execution time of a revset | ||
|
r18644 | Use the --clean option if need to evaluate the impact of build volatile | ||
|
r18239 | revisions set cache on the revset execution. Volatile cache hold filtered | ||
and obsolete related cache.""" | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r18062 | def d(): | ||
|
r18239 | if clear: | ||
repo.invalidatevolatilesets() | ||||
|
r27072 | if contexts: | ||
for ctx in repo.set(expr): pass | ||||
else: | ||||
for r in repo.revs(expr): pass | ||||
|
r18062 | timer(d) | ||
|
r23171 | fm.end() | ||
|
r18240 | |||
|
r25494 | @command('perfvolatilesets', formatteropts) | ||
def perfvolatilesets(ui, repo, *names, **opts): | ||||
|
r18240 | """benchmark the computation of various volatile set | ||
Volatile set computes element related to filtering and obsolescence.""" | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r18240 | repo = repo.unfiltered() | ||
def getobs(name): | ||||
def d(): | ||||
repo.invalidatevolatilesets() | ||||
obsolete.getrevs(repo, name) | ||||
return d | ||||
|
r18241 | allobs = sorted(obsolete.cachefuncs) | ||
if names: | ||||
allobs = [n for n in allobs if n in names] | ||||
for name in allobs: | ||||
|
r18240 | timer(getobs(name), title=name) | ||
def getfiltered(name): | ||||
def d(): | ||||
repo.invalidatevolatilesets() | ||||
|
r20205 | repoview.filterrevs(repo, name) | ||
|
r18240 | return d | ||
|
r18241 | allfilter = sorted(repoview.filtertable) | ||
if names: | ||||
allfilter = [n for n in allfilter if n in names] | ||||
for name in allfilter: | ||||
|
r18240 | timer(getfiltered(name), title=name) | ||
|
r23171 | fm.end() | ||
|
r18304 | |||
@command('perfbranchmap', | ||||
[('f', 'full', False, | ||||
'Includes build time of subset'), | ||||
|
r25494 | ] + formatteropts) | ||
def perfbranchmap(ui, repo, full=False, **opts): | ||||
|
r18304 | """benchmark the update of a branchmap | ||
This benchmarks the full repo.branchmap() call with read and write disabled | ||||
""" | ||||
|
r25494 | timer, fm = gettimer(ui, opts) | ||
|
r18304 | def getbranchmap(filtername): | ||
"""generate a benchmark function for the filtername""" | ||||
if filtername is None: | ||||
view = repo | ||||
else: | ||||
view = repo.filtered(filtername) | ||||
def d(): | ||||
if full: | ||||
view._branchcaches.clear() | ||||
else: | ||||
view._branchcaches.pop(filtername, None) | ||||
view.branchmap() | ||||
return d | ||||
# add filter in smaller subset to bigger subset | ||||
possiblefilters = set(repoview.filtertable) | ||||
|
r30144 | subsettable = getbranchmapsubsettable() | ||
|
r18304 | allfilters = [] | ||
while possiblefilters: | ||||
for name in possiblefilters: | ||||
|
r30144 | subset = subsettable.get(name) | ||
|
r18304 | if subset not in possiblefilters: | ||
break | ||||
else: | ||||
assert False, 'subset cycle %s!' % possiblefilters | ||||
allfilters.append(name) | ||||
possiblefilters.remove(name) | ||||
# warm the cache | ||||
if not full: | ||||
for name in allfilters: | ||||
repo.filtered(name).branchmap() | ||||
# add unfiltered | ||||
allfilters.append(None) | ||||
|
r30145 | |||
branchcacheread = safeattrsetter(branchmap, 'read') | ||||
branchcachewrite = safeattrsetter(branchmap.branchcache, 'write') | ||||
branchcacheread.set(lambda repo: None) | ||||
branchcachewrite.set(lambda bc, repo: None) | ||||
|
r18304 | try: | ||
for name in allfilters: | ||||
timer(getbranchmap(name), title=str(name)) | ||||
finally: | ||||
|
r30145 | branchcacheread.restore() | ||
branchcachewrite.restore() | ||||
|
r23171 | fm.end() | ||
|
r23485 | |||
@command('perfloadmarkers') | ||||
def perfloadmarkers(ui, repo): | ||||
"""benchmark the time to parse the on-disk markers for a repo | ||||
Result is the number of markers in the repo.""" | ||||
timer, fm = gettimer(ui) | ||||
|
r30146 | svfs = getsvfs(repo) | ||
timer(lambda: len(obsolete.obsstore(svfs))) | ||||
|
r23485 | fm.end() | ||
|
r27286 | |||
@command('perflrucachedict', formatteropts + | ||||
[('', 'size', 4, 'size of cache'), | ||||
('', 'gets', 10000, 'number of key lookups'), | ||||
('', 'sets', 10000, 'number of key sets'), | ||||
('', 'mixed', 10000, 'number of mixed mode operations'), | ||||
('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')], | ||||
norepo=True) | ||||
def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000, | ||||
mixedgetfreq=50, **opts): | ||||
def doinit(): | ||||
for i in xrange(10000): | ||||
util.lrucachedict(size) | ||||
values = [] | ||||
for i in xrange(size): | ||||
values.append(random.randint(0, sys.maxint)) | ||||
# Get mode fills the cache and tests raw lookup performance with no | ||||
# eviction. | ||||
getseq = [] | ||||
for i in xrange(gets): | ||||
getseq.append(random.choice(values)) | ||||
def dogets(): | ||||
d = util.lrucachedict(size) | ||||
for v in values: | ||||
d[v] = v | ||||
for key in getseq: | ||||
value = d[key] | ||||
value # silence pyflakes warning | ||||
# Set mode tests insertion speed with cache eviction. | ||||
setseq = [] | ||||
for i in xrange(sets): | ||||
setseq.append(random.randint(0, sys.maxint)) | ||||
def dosets(): | ||||
d = util.lrucachedict(size) | ||||
for v in setseq: | ||||
d[v] = v | ||||
# Mixed mode randomly performs gets and sets with eviction. | ||||
mixedops = [] | ||||
for i in xrange(mixed): | ||||
r = random.randint(0, 100) | ||||
if r < mixedgetfreq: | ||||
op = 0 | ||||
else: | ||||
op = 1 | ||||
mixedops.append((op, random.randint(0, size * 2))) | ||||
def domixed(): | ||||
d = util.lrucachedict(size) | ||||
for op, v in mixedops: | ||||
if op == 0: | ||||
try: | ||||
d[v] | ||||
except KeyError: | ||||
pass | ||||
else: | ||||
d[v] = v | ||||
benches = [ | ||||
(doinit, 'init'), | ||||
(dogets, 'gets'), | ||||
(dosets, 'sets'), | ||||
(domixed, 'mixed') | ||||
] | ||||
for fn, title in benches: | ||||
timer, fm = gettimer(ui, opts) | ||||
timer(fn, title=title) | ||||
fm.end() | ||||
|
r29495 | |||
def uisetup(ui): | ||||
if (util.safehasattr(cmdutil, 'openrevlog') and | ||||
not util.safehasattr(commands, 'debugrevlogopts')): | ||||
# for "historical portability": | ||||
# In this case, Mercurial should be 1.9 (or a79fea6b3e77) - | ||||
# 3.7 (or 5606f7d0d063). Therefore, '--dir' option for | ||||
# openrevlog() should cause failure, because it has been | ||||
# available since 3.5 (or 49c583ca48c4). | ||||
def openrevlog(orig, repo, cmd, file_, opts): | ||||
if opts.get('dir') and not util.safehasattr(repo, 'dirlog'): | ||||
raise error.Abort("This version doesn't support --dir option", | ||||
hint="use 3.5 or later") | ||||
return orig(repo, cmd, file_, opts) | ||||
extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog) | ||||