Source code for schrodinger.test.stu.process_management
"""
Manage processes started by STU. This includes killing and printing
status information.
"""
import datetime
import os
import subprocess
import psutil
from schrodinger.infra import mmjob
[docs]def process_status(process):
process_memory = process.memory_percent()
process_cpu = process.cpu_percent()
process_processes = 1
for child in process.children(recursive=True):
try:
process_memory += child.memory_percent()
process_cpu += child.cpu_percent()
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
process_processes += 1
return dict(process_memory=process_memory,
total_memory=psutil.virtual_memory().percent,
process_cpu=process_cpu,
total_cpu=psutil.cpu_percent(),
process_processes=process_processes,
total_processes=len(psutil.pids()))
def _format_psutil_process(process):
"""Pretty-print a psutil.Process object."""
cmd = subprocess.list2cmdline(process.cmdline())
date = datetime.datetime.fromtimestamp(process.create_time())
name = process.name()
if name in ('perl', 'python') and len(process.cmdline()) > 1:
name += ' ' + os.path.basename(process.cmdline()[1])
return '{} [{}] started {}: {} (status: {})'.format(process.pid, name, date,
cmd, process.status())
[docs]def process_tree_lines(process, maxdepth=100, level=0, ignore_zombies=False):
"""
Recursive internal method to print the current process tree starting at
process
"""
try:
if ignore_zombies and process.status() == 'zombie':
return
yield ('^ ' * level) + _format_psutil_process(process)
except (psutil.NoSuchProcess, psutil.AccessDenied):
# Process is exiting
if not ignore_zombies:
yield ('^ ' * level) + f'process exiting {process.pid}'
return
if level > maxdepth:
return
level += 1
for child in process.children(recursive=False):
yield from process_tree_lines(child,
maxdepth,
level,
ignore_zombies=ignore_zombies)
[docs]def kill_process_children(process, timeout=3):
"""Fatally wound and terminate all child subprocesses of 'process'.
Based on https://code.google.com/p/psutil/source/browse/test/test_psutil.py#257
"""
#collect children first because the process generation is lazy
child_procs = set(process.children(recursive=True))
for child_proc in child_procs:
try:
child_proc.terminate()
except psutil.NoSuchProcess:
pass
gone, alive = psutil.wait_procs(child_procs, timeout=timeout)
for child_proc in alive:
try:
child_proc.kill()
except psutil.NoSuchProcess:
pass
_, alive = psutil.wait_procs(alive, timeout=timeout)
alive_info = [f'{p.pid}:{p.name()}' for p in alive]
assert len(alive) == 0, 'Failed to kill all children of {}: {}'.format(
process.pid, ' '.join(alive_info))
return True
[docs]def kill_launched_jobcontrol_jobs(stu_test_id: str) -> str:
"""
Kill all jobcontrol jobs launched by processes associated with a specific
STU test, designated by stu_test_id.
:return: Descriptive messages about which jobs were cancelling. Empty str
if no jobs were cancelled.
"""
# STU sets this variable in the environment of each test that it launches
# JobControl returns environment variables as single strings
# like "VARIABLENAME=value"
test_id_env = f'SCHRODINGER_STU_TEST_ID={stu_test_id}'
messages = ""
latest_jobs = mmjob.LatestJobs()
latest_jobs.update()
for job in latest_jobs.getUpdatedJobs():
if job.isComplete():
continue
if test_id_env in job.Envs:
messages += f"canceling {job}\n"
job.cancel()
return messages