2016-08-15 02:30:44 +03:00
|
|
|
# profiling.py - profiling functions
|
|
|
|
#
|
|
|
|
# Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
|
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
|
|
|
|
from __future__ import absolute_import, print_function
|
|
|
|
|
2016-08-15 04:25:22 +03:00
|
|
|
import contextlib
|
2016-08-15 02:30:44 +03:00
|
|
|
|
|
|
|
from .i18n import _
|
|
|
|
from . import (
|
2017-01-15 10:47:05 +03:00
|
|
|
encoding,
|
2016-08-15 02:30:44 +03:00
|
|
|
error,
|
2017-05-22 11:17:49 +03:00
|
|
|
extensions,
|
2016-08-15 02:30:44 +03:00
|
|
|
util,
|
|
|
|
)
|
|
|
|
|
2017-05-22 11:17:49 +03:00
|
|
|
def _loadprofiler(ui, profiler):
|
|
|
|
"""load profiler extension. return profile method, or None on failure"""
|
|
|
|
extname = profiler
|
|
|
|
extensions.loadall(ui, whitelist=[extname])
|
|
|
|
try:
|
|
|
|
mod = extensions.find(extname)
|
|
|
|
except KeyError:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return getattr(mod, 'profile', None)
|
|
|
|
|
2016-08-15 04:25:22 +03:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def lsprofile(ui, fp):
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
format = ui.config('profiling', 'format')
|
|
|
|
field = ui.config('profiling', 'sort')
|
|
|
|
limit = ui.configint('profiling', 'limit')
|
|
|
|
climit = ui.configint('profiling', 'nested')
|
2016-08-15 02:30:44 +03:00
|
|
|
|
|
|
|
if format not in ['text', 'kcachegrind']:
|
|
|
|
ui.warn(_("unrecognized profiling format '%s'"
|
|
|
|
" - Ignored\n") % format)
|
|
|
|
format = 'text'
|
|
|
|
|
|
|
|
try:
|
|
|
|
from . import lsprof
|
|
|
|
except ImportError:
|
|
|
|
raise error.Abort(_(
|
|
|
|
'lsprof not available - install from '
|
|
|
|
'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
|
|
|
|
p = lsprof.Profiler()
|
|
|
|
p.enable(subcalls=True)
|
|
|
|
try:
|
2016-08-15 04:25:22 +03:00
|
|
|
yield
|
2016-08-15 02:30:44 +03:00
|
|
|
finally:
|
|
|
|
p.disable()
|
|
|
|
|
|
|
|
if format == 'kcachegrind':
|
|
|
|
from . import lsprofcalltree
|
|
|
|
calltree = lsprofcalltree.KCacheGrind(p)
|
|
|
|
calltree.output(fp)
|
|
|
|
else:
|
|
|
|
# format == 'text'
|
|
|
|
stats = lsprof.Stats(p.getstats())
|
|
|
|
stats.sort(field)
|
|
|
|
stats.pprint(limit=limit, file=fp, climit=climit)
|
|
|
|
|
2016-08-15 04:25:22 +03:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def flameprofile(ui, fp):
|
2016-08-15 02:30:44 +03:00
|
|
|
try:
|
|
|
|
from flamegraph import flamegraph
|
|
|
|
except ImportError:
|
|
|
|
raise error.Abort(_(
|
|
|
|
'flamegraph not available - install from '
|
|
|
|
'https://github.com/evanhempel/python-flamegraph'))
|
|
|
|
# developer config: profiling.freq
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
freq = ui.configint('profiling', 'freq')
|
2016-08-15 02:30:44 +03:00
|
|
|
filter_ = None
|
|
|
|
collapse_recursion = True
|
|
|
|
thread = flamegraph.ProfileThread(fp, 1.0 / freq,
|
|
|
|
filter_, collapse_recursion)
|
2017-02-16 00:17:39 +03:00
|
|
|
start_time = util.timer()
|
2016-08-15 02:30:44 +03:00
|
|
|
try:
|
|
|
|
thread.start()
|
2016-08-15 04:25:22 +03:00
|
|
|
yield
|
2016-08-15 02:30:44 +03:00
|
|
|
finally:
|
|
|
|
thread.stop()
|
|
|
|
thread.join()
|
|
|
|
print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
|
2017-02-16 00:17:39 +03:00
|
|
|
util.timer() - start_time, thread.num_frames(),
|
2016-08-15 02:30:44 +03:00
|
|
|
thread.num_frames(unique=True)))
|
|
|
|
|
2016-08-15 04:25:22 +03:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def statprofile(ui, fp):
|
2016-11-05 06:50:38 +03:00
|
|
|
from . import statprof
|
2016-08-15 02:30:44 +03:00
|
|
|
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
freq = ui.configint('profiling', 'freq')
|
2016-08-15 02:30:44 +03:00
|
|
|
if freq > 0:
|
2016-08-15 04:28:43 +03:00
|
|
|
# Cannot reset when profiler is already active. So silently no-op.
|
|
|
|
if statprof.state.profile_level == 0:
|
|
|
|
statprof.reset(freq)
|
2016-08-15 02:30:44 +03:00
|
|
|
else:
|
|
|
|
ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
|
|
|
|
|
2016-11-05 06:50:38 +03:00
|
|
|
statprof.start(mechanism='thread')
|
|
|
|
|
2016-08-15 02:30:44 +03:00
|
|
|
try:
|
2016-08-15 04:25:22 +03:00
|
|
|
yield
|
2016-08-15 02:30:44 +03:00
|
|
|
finally:
|
2016-11-05 06:50:38 +03:00
|
|
|
data = statprof.stop()
|
|
|
|
|
codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.
There are 2 changes done manually to fix tests:
[warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
[warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal
Since RedBaron is not confident about how to indent things [2].
[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:
#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import, print_function
import os
import sys
import redbaron
def readpath(path):
with open(path) as f:
return f.read()
def writepath(path, content):
with open(path, 'w') as f:
f.write(content)
_configmethods = {'config', 'configbool', 'configint', 'configbytes',
'configlist', 'configdate'}
def extractstring(rnode):
"""get the string from a RedBaron string or call_argument node"""
while rnode.type != 'string':
rnode = rnode.value
return rnode.value[1:-1] # unquote, "'str'" -> "str"
def uiconfigitems(red):
"""match *.ui.config* pattern, yield (node, method, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
obj = node[-3].value
method = node[-2].value
args = node[-1]
section = args[0].value
name = args[1].value
if (obj in ('ui', 'self') and method in _configmethods
and section.type == 'string' and name.type == 'string'):
entry = (node, method, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def coreconfigitems(red):
"""match coreconfigitem(...) pattern, yield (node, args, section, name)"""
for node in red.find_all('atomtrailers'):
entry = None
try:
args = node[1]
section = args[0].value
name = args[1].value
if (node[0].value == 'coreconfigitem' and section.type == 'string'
and name.type == 'string'):
entry = (node, args, extractstring(section),
extractstring(name))
except Exception:
pass
else:
if entry:
yield entry
def registercoreconfig(cfgred, section, name, defaultrepr):
"""insert coreconfigitem to cfgred AST
section and name are plain string, defaultrepr is a string
"""
# find a place to insert the "coreconfigitem" item
entries = list(coreconfigitems(cfgred))
for node, args, nodesection, nodename in reversed(entries):
if (nodesection, nodename) < (section, name):
# insert after this entry
node.insert_after(
'coreconfigitem(%r, %r,\n'
' default=%s,\n'
')' % (section, name, defaultrepr))
return
def main(argv):
if not argv:
print('Usage: codemod_configitems.py FILES\n'
'For example, FILES could be "{hgext,mercurial}/*/**.py"')
dirname = os.path.dirname
reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))
# register configitems to this destination
cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
cfgred = redbaron.RedBaron(readpath(cfgpath))
# state about what to do
registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
toregister = {} # {(section, name): defaultrepr}
coreconfigs = set() # {(section, name)}, whether it's used in core
# first loop: scan all files before taking any action
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
iscore = ('mercurial' in path) and ('hgext' not in path)
red = redbaron.RedBaron(readpath(path))
# find all repo.ui.config* and ui.config* calls, and collect their
# section, name and default value information.
for node, method, args, section, name in uiconfigitems(red):
if section == 'web':
# [web] section has some weirdness, ignore them for now
continue
defaultrepr = None
key = (section, name)
if len(args) == 2:
if key in registered:
continue
if method == 'configlist':
defaultrepr = 'list'
elif method == 'configbool':
defaultrepr = 'False'
else:
defaultrepr = 'None'
elif len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
# try to understand the "default" value
dnode = args[2].value
if dnode.type == 'name':
if dnode.value in {'None', 'True', 'False'}:
defaultrepr = dnode.value
elif dnode.type == 'string':
defaultrepr = repr(dnode.value[1:-1])
elif dnode.type in ('int', 'float'):
defaultrepr = dnode.value
# inconsistent default
if key in toregister and toregister[key] != defaultrepr:
defaultrepr = None
# interesting to rewrite
if key not in registered:
if defaultrepr is None:
print('[note] %s: %s.%s: unsupported default'
% (path, section, name))
registered.add(key) # skip checking it again
else:
toregister[key] = defaultrepr
if iscore:
coreconfigs.add(key)
# second loop: rewrite files given "toregister" result
for path in argv:
# reconstruct redbaron - trade CPU for memory
red = redbaron.RedBaron(readpath(path))
changed = False
for node, method, args, section, name in uiconfigitems(red):
key = (section, name)
defaultrepr = toregister.get(key)
if defaultrepr is None or key not in coreconfigs:
continue
if len(args) >= 3 and (args[2].target is None or
args[2].target.value == 'default'):
try:
del args[2]
changed = True
except Exception:
# redbaron fails to do the rewrite due to indentation
# see https://github.com/PyCQA/redbaron/issues/100
print('[warn] %s: %s.%s: default needs manual removal'
% (path, section, name))
if key not in registered:
print('registering %s.%s' % (section, name))
registercoreconfig(cfgred, section, name, defaultrepr)
registered.add(key)
if changed:
print('updating %s' % path)
writepath(path, red.dumps())
if toregister:
print('updating configitems.py')
writepath(cfgpath, cfgred.dumps())
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
2017-07-15 00:22:40 +03:00
|
|
|
profformat = ui.config('profiling', 'statformat')
|
2016-11-05 06:50:38 +03:00
|
|
|
|
|
|
|
formats = {
|
|
|
|
'byline': statprof.DisplayFormats.ByLine,
|
|
|
|
'bymethod': statprof.DisplayFormats.ByMethod,
|
|
|
|
'hotpath': statprof.DisplayFormats.Hotpath,
|
|
|
|
'json': statprof.DisplayFormats.Json,
|
2017-02-13 09:28:09 +03:00
|
|
|
'chrome': statprof.DisplayFormats.Chrome,
|
2016-11-05 06:50:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if profformat in formats:
|
|
|
|
displayformat = formats[profformat]
|
|
|
|
else:
|
|
|
|
ui.warn(_('unknown profiler output format: %s\n') % profformat)
|
|
|
|
displayformat = statprof.DisplayFormats.Hotpath
|
|
|
|
|
2017-02-13 09:28:09 +03:00
|
|
|
kwargs = {}
|
|
|
|
|
|
|
|
def fraction(s):
|
2017-06-21 11:46:18 +03:00
|
|
|
if isinstance(s, (float, int)):
|
|
|
|
return float(s)
|
2017-02-13 09:28:09 +03:00
|
|
|
if s.endswith('%'):
|
|
|
|
v = float(s[:-1]) / 100
|
|
|
|
else:
|
|
|
|
v = float(s)
|
|
|
|
if 0 <= v <= 1:
|
|
|
|
return v
|
|
|
|
raise ValueError(s)
|
|
|
|
|
|
|
|
if profformat == 'chrome':
|
|
|
|
showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
|
2017-06-30 04:43:56 +03:00
|
|
|
showmax = ui.configwith(fraction, 'profiling', 'showmax')
|
2017-02-13 09:28:09 +03:00
|
|
|
kwargs.update(minthreshold=showmin, maxthreshold=showmax)
|
2017-06-15 21:10:51 +03:00
|
|
|
elif profformat == 'hotpath':
|
2017-07-02 06:34:27 +03:00
|
|
|
# inconsistent config: profiling.showmin
|
2017-06-15 21:10:51 +03:00
|
|
|
limit = ui.configwith(fraction, 'profiling', 'showmin', 0.05)
|
|
|
|
kwargs['limit'] = limit
|
2017-02-13 09:28:09 +03:00
|
|
|
|
|
|
|
statprof.display(fp, data=data, format=displayformat, **kwargs)
|
2016-08-15 02:30:44 +03:00
|
|
|
|
2017-06-08 03:38:48 +03:00
|
|
|
class profile(object):
|
2016-08-15 04:25:22 +03:00
|
|
|
"""Start profiling.
|
|
|
|
|
|
|
|
Profiling is active when the context manager is active. When the context
|
|
|
|
manager exits, profiling results will be written to the configured output.
|
|
|
|
"""
|
2017-06-09 13:41:47 +03:00
|
|
|
def __init__(self, ui, enabled=True):
|
2017-06-08 03:38:48 +03:00
|
|
|
self._ui = ui
|
|
|
|
self._output = None
|
|
|
|
self._fp = None
|
2017-06-12 18:14:56 +03:00
|
|
|
self._fpdoclose = True
|
2017-06-08 03:38:48 +03:00
|
|
|
self._profiler = None
|
2017-06-09 13:41:47 +03:00
|
|
|
self._enabled = enabled
|
2017-06-09 13:39:53 +03:00
|
|
|
self._entered = False
|
|
|
|
self._started = False
|
2017-06-08 03:38:48 +03:00
|
|
|
|
|
|
|
def __enter__(self):
|
2017-06-09 13:39:53 +03:00
|
|
|
self._entered = True
|
2017-06-09 13:41:47 +03:00
|
|
|
if self._enabled:
|
|
|
|
self.start()
|
2017-06-09 13:42:45 +03:00
|
|
|
return self
|
2017-06-09 13:39:53 +03:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Start profiling.
|
|
|
|
|
|
|
|
The profiling will stop at the context exit.
|
|
|
|
|
|
|
|
If the profiler was already started, this has no effect."""
|
|
|
|
if not self._entered:
|
|
|
|
raise error.ProgrammingError()
|
|
|
|
if self._started:
|
|
|
|
return
|
|
|
|
self._started = True
|
2017-06-08 03:38:48 +03:00
|
|
|
profiler = encoding.environ.get('HGPROF')
|
|
|
|
proffn = None
|
|
|
|
if profiler is None:
|
2017-06-30 04:44:00 +03:00
|
|
|
profiler = self._ui.config('profiling', 'type')
|
2017-06-08 03:38:48 +03:00
|
|
|
if profiler not in ('ls', 'stat', 'flame'):
|
|
|
|
# try load profiler from extension with the same name
|
|
|
|
proffn = _loadprofiler(self._ui, profiler)
|
|
|
|
if proffn is None:
|
|
|
|
self._ui.warn(_("unrecognized profiler '%s' - ignored\n")
|
|
|
|
% profiler)
|
|
|
|
profiler = 'stat'
|
|
|
|
|
|
|
|
self._output = self._ui.config('profiling', 'output')
|
|
|
|
|
2017-06-12 18:21:41 +03:00
|
|
|
try:
|
2017-06-12 18:20:48 +03:00
|
|
|
if self._output == 'blackbox':
|
|
|
|
self._fp = util.stringio()
|
|
|
|
elif self._output:
|
|
|
|
path = self._ui.expandpath(self._output)
|
|
|
|
self._fp = open(path, 'wb')
|
|
|
|
else:
|
|
|
|
self._fpdoclose = False
|
|
|
|
self._fp = self._ui.ferr
|
|
|
|
|
|
|
|
if proffn is not None:
|
|
|
|
pass
|
|
|
|
elif profiler == 'ls':
|
|
|
|
proffn = lsprofile
|
|
|
|
elif profiler == 'flame':
|
|
|
|
proffn = flameprofile
|
|
|
|
else:
|
|
|
|
proffn = statprofile
|
2016-08-15 04:25:22 +03:00
|
|
|
|
2017-06-12 18:20:48 +03:00
|
|
|
self._profiler = proffn(self._ui, self._fp)
|
|
|
|
self._profiler.__enter__()
|
2017-06-12 18:21:41 +03:00
|
|
|
except: # re-raises
|
|
|
|
self._closefp()
|
|
|
|
raise
|
2016-08-15 04:25:22 +03:00
|
|
|
|
2017-06-08 03:38:48 +03:00
|
|
|
def __exit__(self, exception_type, exception_value, traceback):
|
2017-06-12 18:24:10 +03:00
|
|
|
propagate = None
|
2017-06-12 18:25:37 +03:00
|
|
|
if self._profiler is not None:
|
2017-06-12 18:24:10 +03:00
|
|
|
propagate = self._profiler.__exit__(exception_type, exception_value,
|
|
|
|
traceback)
|
2017-06-12 18:25:37 +03:00
|
|
|
if self._output == 'blackbox':
|
|
|
|
val = 'Profile:\n%s' % self._fp.getvalue()
|
|
|
|
# ui.log treats the input as a format string,
|
|
|
|
# so we need to escape any % signs.
|
|
|
|
val = val.replace('%', '%%')
|
|
|
|
self._ui.log('profile', val)
|
2017-06-12 18:15:43 +03:00
|
|
|
self._closefp()
|
2017-06-12 18:24:10 +03:00
|
|
|
return propagate
|
2017-06-12 18:13:35 +03:00
|
|
|
|
|
|
|
def _closefp(self):
|
2017-06-12 18:14:56 +03:00
|
|
|
if self._fpdoclose and self._fp is not None:
|
|
|
|
self._fp.close()
|