Monitors a target process for child processes and spawns profilers for them. Use as a context manager: with ChildProcessMonitor(pid, cli_args, output_pattern) as monitor: # monitoring runs here monitor.wait_for_profilers() # optional: wait before cleanup
| 53 | |
| 54 | |
| 55 | class ChildProcessMonitor: |
| 56 | """ |
| 57 | Monitors a target process for child processes and spawns profilers for them. |
| 58 | |
| 59 | Use as a context manager: |
| 60 | with ChildProcessMonitor(pid, cli_args, output_pattern) as monitor: |
| 61 | # monitoring runs here |
| 62 | monitor.wait_for_profilers() # optional: wait before cleanup |
| 63 | # cleanup happens automatically |
| 64 | """ |
| 65 | |
| 66 | def __init__(self, pid, cli_args, output_pattern): |
| 67 | """ |
| 68 | Initialize the child process monitor. |
| 69 | |
| 70 | Args: |
| 71 | pid: Parent process ID to monitor |
| 72 | cli_args: CLI arguments to pass to child profilers |
| 73 | output_pattern: Pattern for output files (format string with {pid}) |
| 74 | """ |
| 75 | self.parent_pid = pid |
| 76 | self.cli_args = cli_args |
| 77 | self.output_pattern = output_pattern |
| 78 | |
| 79 | self._known_children = set() |
| 80 | self._spawned_profilers = [] |
| 81 | self._lock = threading.Lock() |
| 82 | self._stop_event = threading.Event() |
| 83 | self._monitor_thread = None |
| 84 | self._poll_count = 0 |
| 85 | |
| 86 | def __enter__(self): |
| 87 | self._monitor_thread = threading.Thread( |
| 88 | target=self._monitor_loop, |
| 89 | daemon=True, |
| 90 | name=f"child-monitor-{self.parent_pid}", |
| 91 | ) |
| 92 | self._monitor_thread.start() |
| 93 | return self |
| 94 | |
| 95 | def __exit__(self, exc_type, exc_val, exc_tb): |
| 96 | self._stop_event.set() |
| 97 | if self._monitor_thread is not None: |
| 98 | self._monitor_thread.join(timeout=2.0) |
| 99 | if self._monitor_thread.is_alive(): |
| 100 | print( |
| 101 | "Warning: Monitor thread did not stop cleanly", |
| 102 | file=sys.stderr, |
| 103 | ) |
| 104 | |
| 105 | # Wait for child profilers to complete naturally |
| 106 | self.wait_for_profilers() |
| 107 | |
| 108 | # Terminate any remaining profilers |
| 109 | with self._lock: |
| 110 | profilers_to_cleanup = list(self._spawned_profilers) |
| 111 | self._spawned_profilers.clear() |
| 112 |
no outgoing calls
searching dependent graphs…