profiling.py
164 lines
| 4.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 | import os | ||
import sys | ||||
import time | ||||
from .i18n import _ | ||||
from . import ( | ||||
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) | ||||
start_time = time.clock() | ||||
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.' % ( | ||||
time.clock() - start_time, thread.num_frames(), | ||||
thread.num_frames(unique=True))) | ||||
Gregory Szorc
|
r29783 | @contextlib.contextmanager | ||
def statprofile(ui, fp): | ||||
Gregory Szorc
|
r29781 | try: | ||
import statprof | ||||
except ImportError: | ||||
raise error.Abort(_( | ||||
'statprof not available - install using "easy_install statprof"')) | ||||
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) | ||||
statprof.start() | ||||
try: | ||||
Gregory Szorc
|
r29783 | yield | ||
Gregory Szorc
|
r29781 | finally: | ||
statprof.stop() | ||||
statprof.display(fp) | ||||
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. | ||||
""" | ||||
Gregory Szorc
|
r29781 | profiler = os.getenv('HGPROF') | ||
if profiler is None: | ||||
profiler = ui.config('profiling', 'type', default='ls') | ||||
if profiler not in ('ls', 'stat', 'flame'): | ||||
ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler) | ||||
profiler = 'ls' | ||||
output = ui.config('profiling', 'output') | ||||
if output == 'blackbox': | ||||
fp = util.stringio() | ||||
elif output: | ||||
path = ui.expandpath(output) | ||||
fp = open(path, 'wb') | ||||
else: | ||||
fp = sys.stderr | ||||
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 | ||||