This is the solution I used:
#!/usr/bin/env python3 import multiprocessing import selectors import os import array import fcntl import termios import subprocess import decorator import locale import io import codecs import re import collections def strace(function): StraceReturn = collections.namedtuple("StraceReturn", ["return_data", "pid", "strace_data"]) def strace_filter(stracefile, pid, exclude_system=False): system = ( "/bin" , "/boot" , "/dev" , "/etc" , "/lib" , "/proc" , "/root" , "/run" , "/sbin" , "/srv" , "/sys" , "/tmp" , "/usr" , "/var" ) encoding = locale.getpreferredencoding(False) for line in stracefile: match = re.search(r'^(?:\[pid\s+(\d+)\]\s+)?open\(\"((?:\\x[0-9a-f]{2})+)\",', line, re.IGNORECASE) if match: p, f = match.groups(pid) f = codecs.escape_decode(f.encode("ascii"))[0].decode(encoding) if exclude_system and f.startswith(system): continue yield (p, f) def strace_reader(conn_parent, conn_child, barrier, pid): conn_parent.close() encoding = locale.getpreferredencoding(False) strace_args = ["strace", "-e", "open", "-f", "-s", "512", "-xx", "-p", str(pid)] process_data = io.StringIO() process = subprocess.Popen\ ( strace_args , stdout = subprocess.DEVNULL , stderr = subprocess.PIPE , universal_newlines = True ) selector = selectors.DefaultSelector() selector.register(process.stderr, selectors.EVENT_READ) selector.select() barrier.wait() selector.register(conn_child, selectors.EVENT_READ) while len(selector.get_map()): events = selector.select() for key, mask in events: if key.fd == conn_child.fileno(): conn_child.recv() selector.unregister(key.fd) process.terminate() try: process.wait(5) except TimeoutError: process.kill() process.wait() else: ioctl_buffer = array.array("i", [0]) try: fcntl.ioctl(key.fd, termios.FIONREAD, ioctl_buffer) except OSError: read_bytes = 1024 else: read_bytes = max(1024, ioctl_buffer[0]) data = os.read(key.fd, read_bytes) if data:
Note that all information received after the completion event is generated is generated. To avoid this, use the while not signaled and end the subprocess after the loop (FIONREAD ioctl is a break in this case, I did not see any reason to remove it).
Looking back, the decorator could be greatly simplified if I used a temporary file rather than a multiprocessor / pipe.
The child process forks into fork strace - in other words, strace tracks its grandfather. Some Linux distributions allow strace keep track of their children. I'm not sure how to get around this limitation - if the main program continues execution in the child fork (while the parent execs strace ), it is probably a bad idea - the program will trade PIDs like hot potatoes if decorated functions are used too often.