Source code for schrodinger.test.performance.general
"""
General tools for performance tests.
"""
import functools
import subprocess
import time
import timeit
import psutil
#Default maximum time is 5 minutes.
MAX_TIME = 5 * 60
#Directory where input files are kept, added by test runner.
# INPUTFILE_DIR = None
[docs]def get_process_name(process):
"""Pretty-print a psutil.Process object."""
cmd = subprocess.list2cmdline(process.cmdline)
if not cmd:
cmd = f'[{process.name}]'
return f'PID{process.pid}: {cmd}'
[docs]def wait_for_successful_completion(process, timeout=MAX_TIME):
"""
Wait until `process` is complete.
If recursive is true, also wait for all child processes to complete. If the
process is still running after `timeout`, kill it and all its children and
raise an exception. If the exit code is not 0, raise an exception.
:type process: psutil.Process or pid
:param process: Process to watch, waiting for completion.
:type timeout: int
:param timeout: Duration (in seconds) to wait for the process.
:type recursive: bool
:param recursive: Should the child processes of `process` be monitored as
well?
"""
if not isinstance(process, psutil.Process):
process = psutil.Process(process)
try:
return_code = process.wait(timeout=timeout)
except psutil._error.TimeoutExpired:
# If there is an exception, kill all the children.
for child in reversed(process.get_children(recursive=True)):
print("killing", child.pid)
child.terminate()
process.terminate()
raise
if return_code:
name = get_process_name(process)
raise Exception('{} exited with return code {}'.format(
name, return_code))
[docs]def call_in_loop(func, iterations, *args, **kwargs):
"""
Calls function repeatedly to normalize test data.
"""
testable = functools.partial(func, *args, **kwargs)
duration = timeit.timeit(testable, number=iterations)
return duration / iterations
[docs]def wait_and_monitor_usage(process, timeout=MAX_TIME, timeslice=0.1):
'''
Wait until `process` is complete. Query maxrss and cpu time
used by the process and its children every so often (let the
process to run for `timeslice` seconds between observations).
If the process is still running after `timeout`, kill it and all
its children and raise an exception. If the exit code is not 0, raise
an exception.
Returns maxrss (in megabytes) and cpu time (in seconds).
:type process: psutil.Process or pid
:param process: Process to watch, waiting for completion.
:type timeout: float
:param timeout: Duration (in seconds) to wait for the process.
:type timeslice: float
:param timeslice: Interval (in seconds) between observations.
:rtype: (int, float)
:return: Max RSS (in megabytes) and CPU time (user, in seconds).
'''
if not isinstance(process, psutil.Process):
process = psutil.Process(process)
maxrss = 0
cputime = 0
cmdline = process.cmdline()
start_time = time.time()
while time.time() - start_time < timeout:
try:
process.wait(timeslice)
if process.returncode:
raise subprocess.CalledProcessError(process.returncode, cmdline)
return (maxrss / 1024.0 / 1024.0, cputime)
except psutil.TimeoutExpired:
rss, user = 0, 0
for child in process.children(recursive=True):
try:
rss += child.memory_info().rss
user += child.cpu_times().user
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
maxrss = max(maxrss, rss)
cputime = max(cputime, user)
for child in process.children(recursive=True):
child.terminate()
process.terminate()
raise psutil.TimeoutExpired(timeout, process.pid)