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)