profiling: make profiling functions context managers (API)
This makes profiling more flexible since we can now call multiple
functions when a profiler is active. But the real reason for this
is to enable a future consumer to profile a function that returns
a generator. We can't do this from the profiling function itself
because functions can either be generators or have return values:
they can't be both. So therefore it isn't possible to have a generic
profiling function that can both consume and re-emit a generator
and return a value.
--- a/mercurial/dispatch.py Sun Aug 14 16:35:58 2016 -0700
+++ b/mercurial/dispatch.py Sun Aug 14 18:25:22 2016 -0700
@@ -909,7 +909,8 @@
raise error.CommandError(cmd, _("invalid arguments"))
if ui.configbool('profiling', 'enabled'):
- return profiling.profile(ui, checkargs)
+ with profiling.profile(ui):
+ return checkargs()
else:
return checkargs()
--- a/mercurial/profiling.py Sun Aug 14 16:35:58 2016 -0700
+++ b/mercurial/profiling.py Sun Aug 14 18:25:22 2016 -0700
@@ -7,6 +7,7 @@
from __future__ import absolute_import, print_function
+import contextlib
import os
import sys
import time
@@ -17,7 +18,8 @@
util,
)
-def lsprofile(ui, func, fp):
+@contextlib.contextmanager
+def lsprofile(ui, fp):
format = ui.config('profiling', 'format', default='text')
field = ui.config('profiling', 'sort', default='inlinetime')
limit = ui.configint('profiling', 'limit', default=30)
@@ -37,7 +39,7 @@
p = lsprof.Profiler()
p.enable(subcalls=True)
try:
- return func()
+ yield
finally:
p.disable()
@@ -51,7 +53,8 @@
stats.sort(field)
stats.pprint(limit=limit, file=fp, climit=climit)
-def flameprofile(ui, func, fp):
+@contextlib.contextmanager
+def flameprofile(ui, fp):
try:
from flamegraph import flamegraph
except ImportError:
@@ -67,7 +70,7 @@
start_time = time.clock()
try:
thread.start()
- func()
+ yield
finally:
thread.stop()
thread.join()
@@ -75,7 +78,8 @@
time.clock() - start_time, thread.num_frames(),
thread.num_frames(unique=True)))
-def statprofile(ui, func, fp):
+@contextlib.contextmanager
+def statprofile(ui, fp):
try:
import statprof
except ImportError:
@@ -90,13 +94,18 @@
statprof.start()
try:
- return func()
+ yield
finally:
statprof.stop()
statprof.display(fp)
-def profile(ui, fn):
- """Profile a function call."""
+@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.
+ """
profiler = os.getenv('HGPROF')
if profiler is None:
profiler = ui.config('profiling', 'type', default='ls')
@@ -116,11 +125,15 @@
try:
if profiler == 'ls':
- return lsprofile(ui, fn, fp)
+ proffn = lsprofile
elif profiler == 'flame':
- return flameprofile(ui, fn, fp)
+ proffn = flameprofile
else:
- return statprofile(ui, fn, fp)
+ proffn = statprofile
+
+ with proffn(ui, fp):
+ yield
+
finally:
if output:
if output == 'blackbox':