""" `xontrib_tractor_diag`: pytest/tractor diagnostic aliases. All aliases live under the `acli.` namespace so xonsh's prefix-completion treats them as a sub-cmd group — type `acli.` to see the full set. Provides: - `acli.ptree ` psutil-backed proc tree, live + zombies split. - `acli.hung_dump [...]` kernel `wchan`/`stack` + `py-spy dump` (incl `--locals`) for each pid in tree. - `acli.bindspace_scan [|]` find orphaned tractor UDS sock files (no live owner pid). bare name -> `$XDG_RUNTIME_DIR/` (e.g. `piker`, `tractor`); path -> use as-is. default: `$XDG_RUNTIME_DIR/tractor`. - `acli.dump_all [--out-dir] full snapshot bundle — [--label]` ptree + hung_dump + bindspace written to a timestamped dir for sharing / AI introspection. - `acli.reap [opts]` SC-polite zombie-subactor reaper + optional `/dev/shm/` + UDS sock-file sweeps. alias for `scripts/tractor-reap`. Loading from repo root: xontrib load -p ./xontrib tractor_diag Or source directly: source ./xontrib/tractor_diag.xsh Pipe-to-paste idiom (xonsh): acli.hung_dump pytest |t /tmp/hung.log The diagnostic core lives in `tractor._testing.trace` so it can also be invoked from inside pytest tests (e.g. via `fail_after_w_trace` / `afk_alarm_w_trace` capture-on-hang helpers) — these aliases are just thin terminal wrappers. Requires `psutil` for full functionality (`ptree` and the `hung_dump` tree-walk). Falls back to `pgrep -P` recursion if missing. """ from pathlib import Path from tractor._testing.trace import ( dump_all as _dump_all, dump_hung_state, dump_proc_tree, resolve_pids, scan_bindspace, ) # --- ptree ---------------------------------------------------- def _ptree(args): ''' psutil-backed proc tree; per-proc classification into severity-ordered buckets so leaked / defunct procs don't hide in the noise of normal `live` rows. usage: acli.ptree [--tree|-t] [...] See `tractor._testing.trace.dump_proc_tree()` for the bucket semantics + classification details. ''' flag_tree: bool = False pos_args: list = [] for a in args: if a in ('--tree', '-t'): flag_tree = True else: pos_args.append(a) if not pos_args: print('usage: acli.ptree [--tree|-t] [...]') return 1 roots: list = [] for a in pos_args: roots.extend(resolve_pids(a)) roots = sorted(set(roots)) if not roots: print(f'(no procs match: {pos_args})') return 1 print(dump_proc_tree(roots, flag_tree=flag_tree), end='') # --- hung-dump ----------------------------------------------- def _hung_dump(args): ''' kernel + python state for a hung pytest/tractor tree. walks all descendants of each `` arg. usage: acli.hung_dump [...] note: `/proc//stack` and `py-spy dump` typically require CAP_SYS_PTRACE — invoked via `sudo -n`. If sudo isn't cached this alias prompts (via `sudo -v`); for the non-interactive equivalent see `tractor._testing.trace.dump_hung_state(allow_sudo_prompt=False)`. ''' if not args: print('usage: acli.hung_dump [...]') return 1 roots: list = [] for a in args: roots.extend(resolve_pids(a)) roots = sorted(set(roots)) if not roots: print(f'(no procs match: {args})') return 1 print( dump_hung_state(roots, allow_sudo_prompt=True), end='', ) # --- bindspace-scan ------------------------------------------ def _bindspace_scan(args): ''' Scan a tractor UDS bindspace dir for orphan sock files. usage: acli.bindspace_scan [|] See `tractor._testing.trace.scan_bindspace()` for full arg semantics + output-bucket details. ''' arg: str | None = args[0] if args else None print(scan_bindspace(arg), end='') # --- dump-all (snapshot bundle) ------------------------------ def _dump_all_alias(args): ''' Capture a full diag snapshot bundle for a hung proc-tree into a timestamped directory for offline / AI inspection. usage: acli.dump_all [--label