3
\$\begingroup\$

I have created a cumulative profiler, meaning it runs a function several times in a row and prints the cumulative profiler results.

it's python >= 3.6 specific, but you can remove nonlocal and the type hints for it work on older versions.

import cProfile, pstats
from typing import Union, List, Callable, Optional, Tuple, Any

DEFAULT_AMOUNT = 5

class ProfileFunc:
    def __init__(self, func: Callable, sort_stats_by: str):
        self.func: Callable = func
        self.profile_runs: List[cProfile.Profile] = []
        self.sort_stats_by: str = sort_stats_by

    def __call__(self, *args, **kwargs) -> Tuple[Any, pstats.Stats]:
        pr = cProfile.Profile()
        pr.enable()  # this is the profiling section
        retval = self.func(*args, **kwargs)
        pr.disable()

        self.profile_runs.append(pr)
        ps = pstats.Stats(*self.profile_runs).sort_stats(self.sort_stats_by)
        return retval, ps


def cumulative_profiler(amount_of_times: Optional[Union[Callable, int]] = DEFAULT_AMOUNT,
                        sort_stats_by: str='time') -> Callable:

    def real_decorator(func: Callable):
        def wrapper(*args, **kwargs):
            nonlocal func, amount_of_times, sort_stats_by    

            profiled_func = ProfileFunc(func, sort_stats_by)
            assert amount_of_times > 0, 'Cant profile for less then 1 run'
            for i in range(amount_of_times):
                retval, ps = profiled_func(*args, **kwargs)
            ps.print_stats()
            return retval  # returns the results of the function

        return wrapper

    # in case you don't want to specify the amount of times
    if callable(amount_of_times):
        the_func = amount_of_times  # amount_of_times is the function in here
        global DEFAULT_AMOUNT
        amount_of_times = DEFAULT_AMOUNT
        return real_decorator(the_func)
    return real_decorator

Example

profiling the function baz

import time

@cumulative_profiler
def baz():
    time.sleep(1)
    time.sleep(2)
    return 1

baz()

baz ran 5 times and printed this:

         20 function calls in 15.003 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10   15.003    1.500   15.003    1.500 {built-in method time.sleep}
        5    0.000    0.000   15.003    3.001 <ipython-input-9-c89afe010372>:3(baz)
        5    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

specifying the amount of times

@cumulative_profiler(3)
def baz():
    ...

One small problem is that pstats.Stats is called each time instead of only one time in the end. however I don't think it's an expensive operation and it doesn't affect the results.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.