Mercurial > hg
changeset 51761:812a094a7477
profiling: add a py-spy profiling backend
The recommended way to use this backend is by setting the config
`profiling.output` to point to a file because py-spy output is not
human-readable.
author | Arseniy Alekseyev <aalekseyev@janestreet.com> |
---|---|
date | Thu, 01 Aug 2024 13:07:13 +0100 |
parents | 421c9b3f2f4e |
children | dcbe7fda53e4 |
files | mercurial/configitems.toml mercurial/profiling.py |
diffstat | 2 files changed, 64 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/configitems.toml Thu Aug 01 13:38:31 2024 +0100 +++ b/mercurial/configitems.toml Thu Aug 01 13:07:13 2024 +0100 @@ -1811,6 +1811,20 @@ default = "stat" [[items]] +section = "profiling" +name = "py-spy.exe" +default = "py-spy" + +[[items]] +section = "profiling" +name = "py-spy.freq" +default = 100 + +[[items]] +section = "profiling" +name = "py-spy.format" + +[[items]] section = "progress" name = "assume-tty" default = false
--- a/mercurial/profiling.py Thu Aug 01 13:38:31 2024 +0100 +++ b/mercurial/profiling.py Thu Aug 01 13:07:13 2024 +0100 @@ -7,6 +7,9 @@ import contextlib +import os +import signal +import subprocess from .i18n import _ from .pycompat import ( @@ -175,6 +178,50 @@ fp.flush() +@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() + + class profile: """Start profiling. @@ -214,7 +261,7 @@ proffn = None if profiler is None: profiler = self._ui.config(b'profiling', b'type') - if profiler not in (b'ls', b'stat', b'flame'): + if profiler not in (b'ls', b'stat', b'flame', b'py-spy'): # try load profiler from extension with the same name proffn = _loadprofiler(self._ui, profiler) if proffn is None: @@ -257,6 +304,8 @@ proffn = lsprofile elif profiler == b'flame': proffn = flameprofile + elif profiler == b'py-spy': + proffn = pyspy_profile else: proffn = statprofile