equal
deleted
inserted
replaced
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__() |