(binary: str, sample_interval_s: float, hold_s: float)
| 401 | # Cold client spawn profiling (PTY) - samples both client and its server child |
| 402 | # --------------------------------------------------------------------------- # |
| 403 | def profile_client_spawn(binary: str, sample_interval_s: float, hold_s: float) -> dict | None: |
| 404 | if not _HAVE_PTY: |
| 405 | return None |
| 406 | root = tempfile.mkdtemp(prefix="jcode-spawn-client-") |
| 407 | env = isolated_env(root) |
| 408 | log_path = Path(env["JCODE_HOME"]) / "logs" / f"jcode-{time.strftime('%Y-%m-%d')}.log" |
| 409 | pid = None |
| 410 | master_fd = None |
| 411 | try: |
| 412 | pid, master_fd = pty.fork() |
| 413 | if pid == 0: # child |
| 414 | try: |
| 415 | # 120x40 window |
| 416 | winsize = struct.pack("HHHH", 40, 120, 0, 0) |
| 417 | fcntl.ioctl(0, termios.TIOCSWINSZ, winsize) |
| 418 | except OSError: |
| 419 | pass |
| 420 | os.execve( |
| 421 | binary, |
| 422 | [binary, "--no-update", "--socket", env["JCODE_SOCKET"]], |
| 423 | env, |
| 424 | ) |
| 425 | os._exit(127) |
| 426 | |
| 427 | # parent: monitor the client and (once spawned) the server daemon |
| 428 | t0 = time.perf_counter() |
| 429 | client_mon = ProcessMonitor(pid) |
| 430 | server_mon = None |
| 431 | server_pid = None |
| 432 | server_ready_ms = None |
| 433 | deadline = t0 + hold_s |
| 434 | # drain pty output so the child does not block on a full pipe |
| 435 | while time.perf_counter() < deadline: |
| 436 | r, _, _ = select.select([master_fd], [], [], 0.0) |
| 437 | if r: |
| 438 | try: |
| 439 | os.read(master_fd, 65536) |
| 440 | except OSError: |
| 441 | pass |
| 442 | if not client_mon.poll(): |
| 443 | break |
| 444 | if server_pid is None: |
| 445 | cand = find_child_server(env, exclude={pid, os.getpid()}) |
| 446 | if cand is not None: |
| 447 | server_pid = cand |
| 448 | server_mon = ProcessMonitor(server_pid) |
| 449 | if server_mon is not None: |
| 450 | server_mon.poll() |
| 451 | if server_ready_ms is None and socket_connectable(env["JCODE_SOCKET"]): |
| 452 | server_ready_ms = (time.perf_counter() - t0) * 1000.0 |
| 453 | time.sleep(sample_interval_s) |
| 454 | |
| 455 | client_res = asdict(client_mon.finalize()) |
| 456 | server_res = asdict(server_mon.finalize()) if server_mon else None |
| 457 | profile = parse_startup_profile(log_path) |
| 458 | return { |
| 459 | "client": client_res, |
| 460 | "server": server_res, |
no test coverage detected