# HG changeset patch # User Gregory Szorc # Date 1471224322 25200 # Node ID 5d44197c208b4e26ed2fa1eb627d5dea1ee50220 # Parent 97bfc2e5fba5f83981a5150d1401ecd50ee5f549 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. diff -r 97bfc2e5fba5 -r 5d44197c208b mercurial/dispatch.py --- 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() diff -r 97bfc2e5fba5 -r 5d44197c208b mercurial/profiling.py --- 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':