mercurial/profiling.py
changeset 51757 812a094a7477
parent 51585 1574718fa62f
child 51838 3785814bc2b7
equal deleted inserted replaced
51754:421c9b3f2f4e 51757:812a094a7477
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 
     8 
     9 import contextlib
     9 import contextlib
       
    10 import os
       
    11 import signal
       
    12 import subprocess
    10 
    13 
    11 from .i18n import _
    14 from .i18n import _
    12 from .pycompat import (
    15 from .pycompat import (
    13     open,
    16     open,
    14 )
    17 )
   173 
   176 
   174         statprof.display(fp, data=data, format=displayformat, **kwargs)
   177         statprof.display(fp, data=data, format=displayformat, **kwargs)
   175         fp.flush()
   178         fp.flush()
   176 
   179 
   177 
   180 
       
   181 @contextlib.contextmanager
       
   182 def pyspy_profile(ui, fp):
       
   183     exe = ui.config(b'profiling', b'py-spy.exe')
       
   184 
       
   185     freq = ui.configint(b'profiling', b'py-spy.freq')
       
   186 
       
   187     format = ui.config(b'profiling', b'py-spy.format')
       
   188 
       
   189     fd = fp.fileno()
       
   190 
       
   191     output_path = "/dev/fd/%d" % (fd)
       
   192 
       
   193     my_pid = os.getpid()
       
   194 
       
   195     cmd = [
       
   196         exe,
       
   197         "record",
       
   198         "--pid",
       
   199         str(my_pid),
       
   200         "--native",
       
   201         "--rate",
       
   202         str(freq),
       
   203         "--output",
       
   204         output_path,
       
   205     ]
       
   206 
       
   207     if format:
       
   208         cmd.extend(["--format", format])
       
   209 
       
   210     proc = subprocess.Popen(
       
   211         cmd,
       
   212         pass_fds={fd},
       
   213         stdout=subprocess.PIPE,
       
   214     )
       
   215 
       
   216     _ = proc.stdout.readline()
       
   217 
       
   218     try:
       
   219         yield
       
   220     finally:
       
   221         os.kill(proc.pid, signal.SIGINT)
       
   222         proc.communicate()
       
   223 
       
   224 
   178 class profile:
   225 class profile:
   179     """Start profiling.
   226     """Start profiling.
   180 
   227 
   181     Profiling is active when the context manager is active. When the context
   228     Profiling is active when the context manager is active. When the context
   182     manager exits, profiling results will be written to the configured output.
   229     manager exits, profiling results will be written to the configured output.
   212         self._started = True
   259         self._started = True
   213         profiler = encoding.environ.get(b'HGPROF')
   260         profiler = encoding.environ.get(b'HGPROF')
   214         proffn = None
   261         proffn = None
   215         if profiler is None:
   262         if profiler is None:
   216             profiler = self._ui.config(b'profiling', b'type')
   263             profiler = self._ui.config(b'profiling', b'type')
   217         if profiler not in (b'ls', b'stat', b'flame'):
   264         if profiler not in (b'ls', b'stat', b'flame', b'py-spy'):
   218             # try load profiler from extension with the same name
   265             # try load profiler from extension with the same name
   219             proffn = _loadprofiler(self._ui, profiler)
   266             proffn = _loadprofiler(self._ui, profiler)
   220             if proffn is None:
   267             if proffn is None:
   221                 self._ui.warn(
   268                 self._ui.warn(
   222                     _(b"unrecognized profiler '%s' - ignored\n") % profiler
   269                     _(b"unrecognized profiler '%s' - ignored\n") % profiler
   255                 pass
   302                 pass
   256             elif profiler == b'ls':
   303             elif profiler == b'ls':
   257                 proffn = lsprofile
   304                 proffn = lsprofile
   258             elif profiler == b'flame':
   305             elif profiler == b'flame':
   259                 proffn = flameprofile
   306                 proffn = flameprofile
       
   307             elif profiler == b'py-spy':
       
   308                 proffn = pyspy_profile
   260             else:
   309             else:
   261                 proffn = statprofile
   310                 proffn = statprofile
   262 
   311 
   263             self._profiler = proffn(self._ui, self._fp)
   312             self._profiler = proffn(self._ui, self._fp)
   264             self._profiler.__enter__()
   313             self._profiler.__enter__()