profiling.py
192 lines
| 5.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / profiling.py
Gregory Szorc
|
r29781 | # 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 | ||||
Gregory Szorc
|
r29783 | import contextlib | ||
Gregory Szorc
|
r29781 | |||
from .i18n import _ | ||||
from . import ( | ||||
Pulkit Goyal
|
r30820 | encoding, | ||
Gregory Szorc
|
r29781 | error, | ||
util, | ||||
) | ||||
Gregory Szorc
|
r29783 | @contextlib.contextmanager | ||
def lsprofile(ui, fp): | ||||
Gregory Szorc
|
r29781 | format = ui.config('profiling', 'format', default='text') | ||
field = ui.config('profiling', 'sort', default='inlinetime') | ||||
limit = ui.configint('profiling', 'limit', default=30) | ||||
climit = ui.configint('profiling', 'nested', default=0) | ||||
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: | ||||
Gregory Szorc
|
r29783 | yield | ||
Gregory Szorc
|
r29781 | 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) | ||||
Gregory Szorc
|
r29783 | @contextlib.contextmanager | ||
def flameprofile(ui, fp): | ||||
Gregory Szorc
|
r29781 | 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 | ||||
freq = ui.configint('profiling', 'freq', default=1000) | ||||
filter_ = None | ||||
collapse_recursion = True | ||||
thread = flamegraph.ProfileThread(fp, 1.0 / freq, | ||||
filter_, collapse_recursion) | ||||
Simon Farnsworth
|
r30975 | start_time = util.timer() | ||
Gregory Szorc
|
r29781 | try: | ||
thread.start() | ||||
Gregory Szorc
|
r29783 | yield | ||
Gregory Szorc
|
r29781 | finally: | ||
thread.stop() | ||||
thread.join() | ||||
print('Collected %d stack frames (%d unique) in %2.2f seconds.' % ( | ||||
Simon Farnsworth
|
r30975 | util.timer() - start_time, thread.num_frames(), | ||
Gregory Szorc
|
r29781 | thread.num_frames(unique=True))) | ||
Gregory Szorc
|
r29783 | @contextlib.contextmanager | ||
def statprofile(ui, fp): | ||||
Gregory Szorc
|
r30316 | from . import statprof | ||
Gregory Szorc
|
r29781 | |||
freq = ui.configint('profiling', 'freq', default=1000) | ||||
if freq > 0: | ||||
Gregory Szorc
|
r29785 | # Cannot reset when profiler is already active. So silently no-op. | ||
if statprof.state.profile_level == 0: | ||||
statprof.reset(freq) | ||||
Gregory Szorc
|
r29781 | else: | ||
ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq) | ||||
Gregory Szorc
|
r30316 | statprof.start(mechanism='thread') | ||
Gregory Szorc
|
r29781 | try: | ||
Gregory Szorc
|
r29783 | yield | ||
Gregory Szorc
|
r29781 | finally: | ||
Gregory Szorc
|
r30316 | data = statprof.stop() | ||
profformat = ui.config('profiling', 'statformat', 'hotpath') | ||||
formats = { | ||||
'byline': statprof.DisplayFormats.ByLine, | ||||
'bymethod': statprof.DisplayFormats.ByMethod, | ||||
'hotpath': statprof.DisplayFormats.Hotpath, | ||||
'json': statprof.DisplayFormats.Json, | ||||
Bryan O'Sullivan
|
r30930 | 'chrome': statprof.DisplayFormats.Chrome, | ||
Gregory Szorc
|
r30316 | } | ||
if profformat in formats: | ||||
displayformat = formats[profformat] | ||||
else: | ||||
ui.warn(_('unknown profiler output format: %s\n') % profformat) | ||||
displayformat = statprof.DisplayFormats.Hotpath | ||||
Bryan O'Sullivan
|
r30930 | kwargs = {} | ||
def fraction(s): | ||||
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) | ||||
showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999) | ||||
kwargs.update(minthreshold=showmin, maxthreshold=showmax) | ||||
statprof.display(fp, data=data, format=displayformat, **kwargs) | ||||
Gregory Szorc
|
r29781 | |||
Gregory Szorc
|
r29783 | @contextlib.contextmanager | ||
def profile(ui): | ||||
"""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. | ||||
""" | ||||
Pulkit Goyal
|
r30820 | profiler = encoding.environ.get('HGPROF') | ||
Gregory Szorc
|
r29781 | if profiler is None: | ||
Gregory Szorc
|
r30317 | profiler = ui.config('profiling', 'type', default='stat') | ||
Gregory Szorc
|
r29781 | if profiler not in ('ls', 'stat', 'flame'): | ||
ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler) | ||||
Gregory Szorc
|
r30317 | profiler = 'stat' | ||
Gregory Szorc
|
r29781 | |||
output = ui.config('profiling', 'output') | ||||
if output == 'blackbox': | ||||
fp = util.stringio() | ||||
elif output: | ||||
path = ui.expandpath(output) | ||||
fp = open(path, 'wb') | ||||
else: | ||||
Yuya Nishihara
|
r30322 | fp = ui.ferr | ||
Gregory Szorc
|
r29781 | |||
try: | ||||
if profiler == 'ls': | ||||
Gregory Szorc
|
r29783 | proffn = lsprofile | ||
Gregory Szorc
|
r29781 | elif profiler == 'flame': | ||
Gregory Szorc
|
r29783 | proffn = flameprofile | ||
Gregory Szorc
|
r29781 | else: | ||
Gregory Szorc
|
r29783 | proffn = statprofile | ||
with proffn(ui, fp): | ||||
yield | ||||
Gregory Szorc
|
r29781 | finally: | ||
if output: | ||||
if output == 'blackbox': | ||||
val = 'Profile:\n%s' % fp.getvalue() | ||||
# ui.log treats the input as a format string, | ||||
# so we need to escape any % signs. | ||||
val = val.replace('%', '%%') | ||||
ui.log('profile', val) | ||||
fp.close() | ||||
Gregory Szorc
|
r29784 | |||
@contextlib.contextmanager | ||||
def maybeprofile(ui): | ||||
"""Profile if enabled, else do nothing. | ||||
This context manager can be used to optionally profile if profiling | ||||
is enabled. Otherwise, it does nothing. | ||||
The purpose of this context manager is to make calling code simpler: | ||||
just use a single code path for calling into code you may want to profile | ||||
and this function determines whether to start profiling. | ||||
""" | ||||
if ui.configbool('profiling', 'enabled'): | ||||
with profile(ui): | ||||
yield | ||||
else: | ||||
yield | ||||