##// END OF EJS Templates
config: extra the code showing config in its own module too...
config: extra the code showing config in its own module too This conclude our extract of the main semantic in a dedicated module.

File last commit:

r53297:5ff6fba7 default
r53316:04c3fb88 default
Show More
profiling.py
361 lines | 10.5 KiB | text/x-python | PythonLexer
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
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.
Matt Harbison
typing: add `from __future__ import annotations` to most files...
r52756 from __future__ import annotations
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 import contextlib
Arseniy Alekseyev
profiling: add a py-spy profiling backend...
r52654 import os
import signal
import subprocess
profiling: improve 3.12 error message for calling lsprof twice...
r52733 import sys
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
from .i18n import _
from . import (
Pulkit Goyal
py3: replace pycompat.getenv with encoding.environ.get...
r30820 encoding,
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 error,
Jun Wu
profiling: allow loading profiling extension before everything else...
r32417 extensions,
Matt Harbison
profile: colorize output on Windows...
r36701 pycompat,
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 util,
)
Augie Fackler
formatting: blacken the codebase...
r43346
Jun Wu
profiling: allow loading profiling extension before everything else...
r32417 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)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def lsprofile(ui, fp):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 format = ui.config(b'profiling', b'format')
field = ui.config(b'profiling', b'sort')
limit = ui.configint(b'profiling', b'limit')
climit = ui.configint(b'profiling', b'nested')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if format not in [b'text', b'kcachegrind']:
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 ui.warn(_(b"unrecognized profiling format '%s' - Ignored\n") % format)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 format = b'text'
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
try:
from . import lsprof
except ImportError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'lsprof not available - install from '
b'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 p = lsprof.Profiler()
profiling: improve 3.12 error message for calling lsprof twice...
r52733 try:
p.enable(subcalls=True)
except ValueError as exc:
if str(exc) != "Another profiling tool is already active":
raise
if not hasattr(sys, "monitoring"):
raise
# python >=3.12 prevent more than one profiler to run at the same
# time, tries to improve the report to help the user understand
# what is going on.
other_tool_name = sys.monitoring.get_tool(sys.monitoring.PROFILER_ID)
if other_tool_name == "cProfile":
Matt Harbison
profiling: pass bytes to `_()` and `error.Abort()`...
r52790 msg = b'cannot recursively call `lsprof`'
profiling: improve 3.12 error message for calling lsprof twice...
r52733 raise error.Abort(msg) from None
else:
Matt Harbison
profiling: pass bytes to `_()` and `error.Abort()`...
r52790 tool = b'<unknown>'
if other_tool_name:
tool = encoding.strtolocal(other_tool_name)
m = b'failed to start "lsprofile"; another profiler already running: %s'
raise error.Abort(_(m) % tool) from None
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
p.disable()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if format == b'kcachegrind':
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 from . import lsprofcalltree
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 calltree = lsprofcalltree.KCacheGrind(p)
calltree.output(fp)
else:
# format == 'text'
stats = lsprof.Stats(p.getstats())
Gregory Szorc
py3: convert sorting field to sysstr...
r40228 stats.sort(pycompat.sysstr(field))
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 stats.pprint(limit=limit, file=fp, climit=climit)
profiler: flush after writing the profiler output...
r52481 fp.flush()
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def flameprofile(ui, fp):
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
Matt Harbison
profiling: disable the import-error warning for the flamegraph module...
r44130 from flamegraph import flamegraph # pytype: disable=import-error
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 except ImportError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'flamegraph not available - install from '
b'https://github.com/evanhempel/python-flamegraph'
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 # developer config: profiling.freq
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 freq = ui.configint(b'profiling', b'freq')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 filter_ = None
collapse_recursion = True
Augie Fackler
formatting: blacken the codebase...
r43346 thread = flamegraph.ProfileThread(
fp, 1.0 / freq, filter_, collapse_recursion
)
Simon Farnsworth
mercurial: switch to util.timer for all interval timings...
r30975 start_time = util.timer()
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
thread.start()
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
thread.stop()
thread.join()
profiler: flush after writing the profiler output...
r52481 m = b'Collected %d stack frames (%d unique) in %2.2f seconds.'
m %= (
(
Augie Fackler
formatting: blacken the codebase...
r43346 util.timer() - start_time,
thread.num_frames(),
thread.num_frames(unique=True),
profiler: flush after writing the profiler output...
r52481 ),
Augie Fackler
formatting: blacken the codebase...
r43346 )
profiler: flush after writing the profiler output...
r52481 print(m, flush=True)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 @contextlib.contextmanager
def statprofile(ui, fp):
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 from . import statprof
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 freq = ui.configint(b'profiling', b'freq')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 if freq > 0:
Gregory Szorc
profiling: don't error with statprof when profiling has already started...
r29785 # Cannot reset when profiler is already active. So silently no-op.
if statprof.state.profile_level == 0:
statprof.reset(freq)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.warn(_(b"invalid sampling frequency '%s' - ignoring\n") % freq)
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: blacken the codebase...
r43346 track = ui.config(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'profiling', b'time-track', pycompat.iswindows and b'cpu' or b'real'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 statprof.start(mechanism=b'thread', track=track)
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 try:
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 yield
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781 finally:
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 data = statprof.stop()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 profformat = ui.config(b'profiling', b'statformat')
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316
formats = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'byline': statprof.DisplayFormats.ByLine,
b'bymethod': statprof.DisplayFormats.ByMethod,
b'hotpath': statprof.DisplayFormats.Hotpath,
b'json': statprof.DisplayFormats.Json,
b'chrome': statprof.DisplayFormats.Chrome,
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 }
if profformat in formats:
displayformat = formats[profformat]
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.warn(_(b'unknown profiler output format: %s\n') % profformat)
Gregory Szorc
profiling: use vendored statprof and upstream enhancements (BC)...
r30316 displayformat = statprof.DisplayFormats.Hotpath
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 kwargs = {}
def fraction(s):
profiling: cope with configwith default value handling changes...
r32978 if isinstance(s, (float, int)):
return float(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if s.endswith(b'%'):
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 v = float(s[:-1]) / 100
else:
v = float(s)
if 0 <= v <= 1:
return v
raise ValueError(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if profformat == b'chrome':
showmin = ui.configwith(fraction, b'profiling', b'showmin', 0.005)
showmax = ui.configwith(fraction, b'profiling', b'showmax')
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930 kwargs.update(minthreshold=showmin, maxthreshold=showmax)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif profformat == b'hotpath':
Gregory Szorc
check-config: syntax to allow inconsistent config values...
r33192 # inconsistent config: profiling.showmin
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 limit = ui.configwith(fraction, b'profiling', b'showmin', 0.05)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 kwargs['limit'] = limit
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 showtime = ui.configbool(b'profiling', b'showtime')
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 kwargs['showtime'] = showtime
Bryan O'Sullivan
profiling: add statprof support for Chrome trace viewer rendering...
r30930
statprof.display(fp, data=data, format=displayformat, **kwargs)
profiler: flush after writing the profiler output...
r52481 fp.flush()
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: blacken the codebase...
r43346
Arseniy Alekseyev
profiling: add a py-spy profiling backend...
r52654 @contextlib.contextmanager
def pyspy_profile(ui, fp):
exe = ui.config(b'profiling', b'py-spy.exe')
freq = ui.configint(b'profiling', b'py-spy.freq')
format = ui.config(b'profiling', b'py-spy.format')
fd = fp.fileno()
output_path = "/dev/fd/%d" % (fd)
my_pid = os.getpid()
cmd = [
exe,
"record",
"--pid",
str(my_pid),
"--native",
"--rate",
str(freq),
"--output",
output_path,
]
if format:
cmd.extend(["--format", format])
proc = subprocess.Popen(
cmd,
pass_fds={fd},
stdout=subprocess.PIPE,
)
_ = proc.stdout.readline()
try:
yield
finally:
os.kill(proc.pid, signal.SIGINT)
proc.communicate()
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class profile:
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783 """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.
"""
Augie Fackler
formatting: blacken the codebase...
r43346
profile: introduce a knob to control if the context is actually profiling...
r32785 def __init__(self, ui, enabled=True):
profile: upgrade the "profile" context manager to a full class...
r32783 self._ui = ui
self._output = None
self._fp = None
profile: use explicit logic to control file closing...
r32805 self._fpdoclose = True
Kyle Lippincott
profiling: flush stdout before writing profile to stderr...
r44653 self._flushfp = None
profile: upgrade the "profile" context manager to a full class...
r32783 self._profiler = None
profile: introduce a knob to control if the context is actually profiling...
r32785 self._enabled = enabled
profile: introduce a "start" method to the profile context...
r32784 self._entered = False
self._started = False
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: upgrade the "profile" context manager to a full class...
r32783 def __enter__(self):
profile: introduce a "start" method to the profile context...
r32784 self._entered = True
profile: introduce a knob to control if the context is actually profiling...
r32785 if self._enabled:
self.start()
profile: make the contextmanager object available to the callers...
r32786 return self
profile: introduce a "start" method to the profile context...
r32784
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:
Matt Harbison
profiling: add a missing argument to the ProgrammingError constructor...
r44131 raise error.ProgrammingError(b'use a context manager to start')
profile: introduce a "start" method to the profile context...
r32784 if self._started:
return
self._started = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 profiler = encoding.environ.get(b'HGPROF')
profile: upgrade the "profile" context manager to a full class...
r32783 proffn = None
if profiler is None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 profiler = self._ui.config(b'profiling', b'type')
Arseniy Alekseyev
profiling: add a py-spy profiling backend...
r52654 if profiler not in (b'ls', b'stat', b'flame', b'py-spy'):
profile: upgrade the "profile" context manager to a full class...
r32783 # try load profiler from extension with the same name
proffn = _loadprofiler(self._ui, profiler)
if proffn is None:
Augie Fackler
formatting: blacken the codebase...
r43346 self._ui.warn(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"unrecognized profiler '%s' - ignored\n") % profiler
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 profiler = b'stat'
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._output = self._ui.config(b'profiling', b'output')
Gregory Szorc
profiling: move profiling code from dispatch.py (API)...
r29781
profile: close 'fp' on error within '__enter__'...
r32808 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if self._output == b'blackbox':
profile: indent part of '__enter__'...
r32807 self._fp = util.stringio()
elif self._output:
profiling: use `util.expandpath` instead of `ui.expandpath` for output...
r47720 path = util.expandpath(self._output)
Matt Harbison
profiling: stop using the `pycompat.open()` shim
r53260 self._fp = open(path, 'wb')
Matt Harbison
profile: colorize output on Windows...
r36701 elif pycompat.iswindows:
# parse escape sequence by win32print()
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class uifp:
Matt Harbison
profile: colorize output on Windows...
r36701 def __init__(self, ui):
self._ui = ui
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Harbison
profile: colorize output on Windows...
r36701 def write(self, data):
self._ui.write_err(data)
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Harbison
profile: colorize output on Windows...
r36701 def flush(self):
self._ui.flush()
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Harbison
profile: colorize output on Windows...
r36701 self._fpdoclose = False
self._fp = uifp(self._ui)
profile: indent part of '__enter__'...
r32807 else:
self._fpdoclose = False
self._fp = self._ui.ferr
Kyle Lippincott
profiling: flush stdout before writing profile to stderr...
r44653 # Ensure we've flushed fout before writing to ferr.
self._flushfp = self._ui.fout
profile: upgrade the "profile" context manager to a full class...
r32783
profile: indent part of '__enter__'...
r32807 if proffn is not None:
pass
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif profiler == b'ls':
profile: indent part of '__enter__'...
r32807 proffn = lsprofile
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif profiler == b'flame':
profile: indent part of '__enter__'...
r32807 proffn = flameprofile
Arseniy Alekseyev
profiling: add a py-spy profiling backend...
r52654 elif profiler == b'py-spy':
proffn = pyspy_profile
profile: indent part of '__enter__'...
r32807 else:
proffn = statprofile
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783
profile: indent part of '__enter__'...
r32807 self._profiler = proffn(self._ui, self._fp)
self._profiler.__enter__()
Augie Fackler
formatting: blacken the codebase...
r43346 except: # re-raises
profile: close 'fp' on error within '__enter__'...
r32808 self._closefp()
raise
Gregory Szorc
profiling: make profiling functions context managers (API)...
r29783
profile: upgrade the "profile" context manager to a full class...
r32783 def __exit__(self, exception_type, exception_value, traceback):
profile: properly propagate exception from the sub-context manager...
r32810 propagate = None
profile: close 'fp' in all cases...
r32809 if self._profiler is not None:
Kyle Lippincott
profiling: flush stdout before writing profile to stderr...
r44653 self._uiflush()
Augie Fackler
formatting: blacken the codebase...
r43346 propagate = self._profiler.__exit__(
exception_type, exception_value, traceback
)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if self._output == b'blackbox':
Matt Harbison
profiling: add an assertion to help pytype...
r53297 fp = self._fp
# Help pytype: blackbox output uses io.BytesIO instead of a file
assert isinstance(fp, util.stringio)
val = b'Profile:\n%s' % fp.getvalue()
profile: close 'fp' in all cases...
r32809 # ui.log treats the input as a format string,
# so we need to escape any % signs.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 val = val.replace(b'%', b'%%')
self._ui.log(b'profile', val)
profile: remove now useless indent...
r32806 self._closefp()
profile: properly propagate exception from the sub-context manager...
r32810 return propagate
profiling: move 'fp' closing logic into its own function...
r32804
def _closefp(self):
profile: use explicit logic to control file closing...
r32805 if self._fpdoclose and self._fp is not None:
self._fp.close()
Kyle Lippincott
profiling: flush stdout before writing profile to stderr...
r44653
def _uiflush(self):
if self._flushfp:
self._flushfp.flush()