Index: Lib/profile.py =================================================================== --- Lib/profile.py (revisione 82930) +++ Lib/profile.py (copia locale) @@ -37,6 +37,8 @@ import os import time import marshal +import pstats +import tempfile from optparse import OptionParser __all__ = ["run", "runctx", "Profile"] @@ -575,6 +577,70 @@ #**************************************************************************** + +def profile(sort='cumulative', lines=None, strip_dirs=True): + """A decorator which profiles a callable. + Example usage: + + >>> @profile + ... def factorial(n): + ... n = abs(int(n)) + ... if n < 1: + ... n = 1 + ... x = 1 + ... for i in range(1, n+1): + ... x = i * x + ... return x + ... + >>> factorial(5) + Thu Jul 15 20:58:21 2010 /tmp/tmpIDejr5 + + 4 function calls in 0.000 CPU seconds + + Ordered by: internal time, call count + + ncalls tottime percall cumtime percall filename:lineno(function) + 1 0.000 0.000 0.000 0.000 profiler.py:120(factorial) + 1 0.000 0.000 0.000 0.000 {range} + 1 0.000 0.000 0.000 0.000 {abs} + 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} + + 120 + >>> + """ + def outer(fun): + def inner(*args, **kwargs): + file = tempfile.NamedTemporaryFile() + prof = Profile() + try: + ret = prof.runcall(fun, *args, **kwargs) + except: + file.close() + raise + + prof.dump_stats(file.name) + stats = pstats.Stats(file.name) + if strip_dirs: + stats.strip_dirs() + if isinstance(sort, (tuple, list)): + stats.sort_stats(*sort) + else: + stats.sort_stats(sort) + stats.print_stats(lines) + + file.close() + return ret + return inner + + # in case this is defined as "@profile" instead of "@profile()" + if hasattr(sort, '__call__'): + fun = sort + sort = 'cumulative' + outer = outer(fun) + + return outer + + def main(): usage = "profile.py [-o output_file_path] [-s sort] scriptfile [arg] ..." parser = OptionParser(usage=usage) Index: Lib/cProfile.py =================================================================== --- Lib/cProfile.py (revisione 82930) +++ Lib/cProfile.py (copia locale) @@ -7,7 +7,10 @@ __all__ = ["run", "runctx", "Profile"] import _lsprof +import tempfile +import pstats + # ____________________________________________________________ # Simple interface @@ -155,6 +158,70 @@ # ____________________________________________________________ + +def profile(sort='cumulative', lines=None, strip_dirs=True): + """A decorator which profiles a callable. + Example usage: + + >>> @profile + ... def factorial(n): + ... n = abs(int(n)) + ... if n < 1: + ... n = 1 + ... x = 1 + ... for i in range(1, n+1): + ... x = i * x + ... return x + ... + >>> factorial(5) + Thu Jul 15 20:58:21 2010 /tmp/tmpIDejr5 + + 4 function calls in 0.000 CPU seconds + + Ordered by: internal time, call count + + ncalls tottime percall cumtime percall filename:lineno(function) + 1 0.000 0.000 0.000 0.000 profiler.py:120(factorial) + 1 0.000 0.000 0.000 0.000 {range} + 1 0.000 0.000 0.000 0.000 {abs} + 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} + + 120 + >>> + """ + def outer(fun): + def inner(*args, **kwargs): + file = tempfile.NamedTemporaryFile() + prof = Profile() + try: + ret = prof.runcall(fun, *args, **kwargs) + except: + file.close() + raise + + prof.dump_stats(file.name) + stats = pstats.Stats(file.name) + if strip_dirs: + stats.strip_dirs() + if isinstance(sort, (tuple, list)): + stats.sort_stats(*sort) + else: + stats.sort_stats(sort) + stats.print_stats(lines) + + file.close() + return ret + return inner + + # in case this is defined as "@profile" instead of "@profile()" + if hasattr(sort, '__call__'): + fun = sort + sort = 'cumulative' + outer = outer(fun) + + return outer + + def main(): import os, sys from optparse import OptionParser Index: Lib/test/test_profile.py =================================================================== --- Lib/test/test_profile.py (revisione 82930) +++ Lib/test/test_profile.py (copia locale) @@ -74,7 +74,53 @@ self.assertIn(self.expected_max_output, res, "Profiling {0!r} didn't report max:\n{1}".format(stmt, res)) + def test_decorator(self): + if self.profilerclass is profile.Profile: + from profile import profile as profile_decorator + else: + from cProfile import profile as profile_decorator + def run(): + original_stdout = sys.stdout + try: + f = StringIO() + sys.stdout = f + ret = testfun() + assert ret == 2 + finally: + sys.stdout = original_stdout + f.seek(0) + ret = f.read() + f.close() + return ret + + + @profile_decorator + def testfun(): + return 1 + 1 + run() + + @profile_decorator() + def testfun(): + return 1 + 1 + run() + + @profile_decorator(sort="calls") + def testfun(): + return 1 + 1 + self.assertTrue("Ordered by: call count" in run()) + + @profile_decorator(lines=1) + def testfun(): + return 1 + 1 + run() + + @profile_decorator(strip_dirs=False) + def testfun(): + return 1 + 1 + run() + + def regenerate_expected_output(filename, cls): filename = filename.rstrip('co') print('Regenerating %s...' % filename) @@ -158,3 +204,4 @@ if __name__ == "__main__": main() +