Source code for schrodinger.utils.profiling

"""
Profiling and timing decorators.

For more information on profiling python code, see also
https://internal.schrodinger.com/products/development/docs/software_engineering/profiling.html#profiling-python-code

A very simple example showing the use the profiling and timing decorators::

    import time

    @profiling
    def profiled_function(duration=0.05):
        time.sleep(duration)


    @timing
    def timed_function(duration=0.05):
        time.sleep(duration)


    class Test(object):
        @profiling
        def profiledMethod(self, duration=0.05):
            time.sleep(duration)

        @timing
        def timedMethod(self, duration=0.05):
            time.sleep(duration)


    test = Test()
    # call the profiled method
    test.profiledMethod()

    # call the timed method
    test.timedMethod(0.1)

    # call the profiled function
    profiled_function()

    # call the timed function
    timed_function(0.1)
"""

import atexit
import contextlib
import cProfile
import datetime
import time
import os

import decorator


[docs]@decorator.decorator def profiling(f, *args, **kwargs): """ Decorator that will generate a profile file with the name of the decorated function or method and time stamp. The duration of the execution of decorated function or method will also be printed to the terminal. """ now = datetime.datetime.now() filename = now.strftime(f.__name__ + "_%Y%m%d-%H%M%S.%f.prof") print("%s profile" % os.path.abspath(filename)) retval = profile_call(f, *args, **kwargs, profile_filename=filename) print("{}: {} sec".format(f.__name__, datetime.datetime.now() - now)) return retval
[docs]def timing(f): """ Decorator that will print the duration of the execution of decorated function or method to the terminal every time it's called. Will also report the average time, minimum time, and the total number of calls on program exit. """ f._timings = [] def wrapped_f(*args, **kwargs): start = time.time() retval = f(*args, **kwargs) elapsed_time = time.time() - start print(f"{f.__name__}: {elapsed_time:.3f} sec") f._timings.append(elapsed_time) return retval atexit.register(_report_average_timing, f) return wrapped_f
def _report_average_timing(f): if f._timings: avg_time = sum(f._timings) / len(f._timings) min_time = min(f._timings) max_time = max(f._timings) else: avg_time = 0 min_time = 0 max_time = 0 print(f"TIME [MIN/AVG/MAX] for {f.__name__} over {len(f._timings)} calls:" f" {min_time:.3f}/{avg_time:.3f}/{max_time:.3f} sec")
[docs]def profile_call(func, *args, profile_filename=None, **kwargs): """ Profile the specified function call. :param func: The function to profile. All arguments to `profile_call` other than `profile_filename` will be passed to this function. :type func: Callable :param profile_filename: The name of the file to save the profiling data to. Note that this argument is keyword-only and is required. :type profile_filename: str :return: The value returned by `func`. :rtype: object """ if profile_filename is None: raise ValueError("No filename given.") profiler = cProfile.Profile() retval = profiler.runcall(func, *args, **kwargs) profiler.dump_stats(profile_filename) return retval
[docs]@contextlib.contextmanager def profile(profile_filename='program.prof'): """ Profile a block of code and dump the results into a file. Should be used as a context manager, e.g., with profile(profile_filename='mystats.prof'): execute_some_code() :param str profile_filename: The name of the file to save the profiling data to. :rtype: cProfile.Profile :return: The profiling object being used """ profiler = cProfile.Profile() profiler.enable() yield profiler profiler.disable() profiler.dump_stats(profile_filename)