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)