proctrace 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. proctrace-0.1.0/.cargo/config.toml +9 -0
  2. proctrace-0.1.0/.github/workflows/ci.yml +66 -0
  3. proctrace-0.1.0/.gitignore +34 -0
  4. proctrace-0.1.0/CHANGELOG.md +24 -0
  5. proctrace-0.1.0/Cargo.lock +165 -0
  6. proctrace-0.1.0/Cargo.toml +22 -0
  7. proctrace-0.1.0/GUIDE.md +323 -0
  8. proctrace-0.1.0/PKG-INFO +264 -0
  9. proctrace-0.1.0/README.md +250 -0
  10. proctrace-0.1.0/guide-codesnippet/01_basic_watch.py +22 -0
  11. proctrace-0.1.0/guide-codesnippet/02_selective_tracking.py +37 -0
  12. proctrace-0.1.0/guide-codesnippet/03_probe_decorator.py +56 -0
  13. proctrace-0.1.0/guide-codesnippet/04_json_export.py +52 -0
  14. proctrace-0.1.0/guide-codesnippet/05_fd_leak_detection.py +78 -0
  15. proctrace-0.1.0/guide-codesnippet/06_ipc_tracing.py +89 -0
  16. proctrace-0.1.0/guide-codesnippet/07_signal_dump.py +53 -0
  17. proctrace-0.1.0/guide-codesnippet/08_cli_usage.sh +91 -0
  18. proctrace-0.1.0/guide-codesnippet/README.md +29 -0
  19. proctrace-0.1.0/proctrace/__init__.py +34 -0
  20. proctrace-0.1.0/proctrace/__main__.py +4 -0
  21. proctrace-0.1.0/proctrace/_proctrace_core.pyi +19 -0
  22. proctrace-0.1.0/proctrace/_types.py +101 -0
  23. proctrace-0.1.0/proctrace/cli.py +168 -0
  24. proctrace-0.1.0/proctrace/decorators.py +78 -0
  25. proctrace-0.1.0/proctrace/ipc.py +183 -0
  26. proctrace-0.1.0/proctrace/logger.py +82 -0
  27. proctrace-0.1.0/proctrace/pytest_plugin.py +95 -0
  28. proctrace-0.1.0/proctrace/snapshot.py +126 -0
  29. proctrace-0.1.0/proctrace/watch.py +183 -0
  30. proctrace-0.1.0/pyproject.toml +36 -0
  31. proctrace-0.1.0/src/error.rs +26 -0
  32. proctrace-0.1.0/src/ipc_probe.rs +178 -0
  33. proctrace-0.1.0/src/lib.rs +48 -0
  34. proctrace-0.1.0/src/resources/linux.rs +87 -0
  35. proctrace-0.1.0/src/resources/macos.rs +67 -0
  36. proctrace-0.1.0/src/resources.rs +106 -0
  37. proctrace-0.1.0/src/sampler.rs +65 -0
  38. proctrace-0.1.0/src/signal.rs +53 -0
  39. proctrace-0.1.0/tests/conftest.py +67 -0
  40. proctrace-0.1.0/tests/test_cli.py +229 -0
  41. proctrace-0.1.0/tests/test_decorators.py +74 -0
  42. proctrace-0.1.0/tests/test_integration.py +211 -0
  43. proctrace-0.1.0/tests/test_ipc.py +103 -0
  44. proctrace-0.1.0/tests/test_logger.py +161 -0
  45. proctrace-0.1.0/tests/test_proctrace.py +27 -0
  46. proctrace-0.1.0/tests/test_pytest_plugin.py +39 -0
  47. proctrace-0.1.0/tests/test_snapshot.py +58 -0
  48. proctrace-0.1.0/tests/test_watch.py +110 -0
  49. proctrace-0.1.0/uv.lock +714 -0
@@ -0,0 +1,9 @@
1
+ [build]
2
+ # On macOS, the linker needs this to correctly set the install name of the .dylib
3
+ # so Python can find it regardless of where the venv is installed.
4
+ # On Linux this has no effect.
5
+ [target.x86_64-apple-darwin]
6
+ rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
7
+
8
+ [target.aarch64-apple-darwin]
9
+ rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
@@ -0,0 +1,66 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+ RUST_BACKTRACE: 1
12
+
13
+ jobs:
14
+ test:
15
+ name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-latest]
20
+ python-version: ["3.10", "3.11", "3.12"]
21
+
22
+ runs-on: ${{ matrix.os }}
23
+
24
+ steps:
25
+ - name: Checkout
26
+ uses: actions/checkout@v4
27
+
28
+ - name: Set up Python ${{ matrix.python-version }}
29
+ uses: actions/setup-python@v5
30
+ with:
31
+ python-version: ${{ matrix.python-version }}
32
+
33
+ - name: Set up Rust (stable)
34
+ uses: dtolnay/rust-toolchain@stable
35
+ with:
36
+ components: clippy
37
+
38
+ - name: Cache Rust build
39
+ uses: actions/cache@v4
40
+ with:
41
+ path: |
42
+ ~/.cargo/registry
43
+ ~/.cargo/git
44
+ target/
45
+ key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }}
46
+
47
+ - name: Install Python build tools
48
+ run: pip install maturin>=1.5 pytest>=8 pytest-asyncio twine
49
+
50
+ - name: Build and install (maturin develop)
51
+ run: maturin develop --release
52
+
53
+ - name: Install package in editable mode
54
+ run: pip install -e ".[dev]"
55
+
56
+ - name: Run Clippy (Rust linter)
57
+ run: cargo clippy -- -D warnings
58
+
59
+ - name: Run tests
60
+ run: pytest tests/ -v --tb=short
61
+
62
+ - name: Build release wheel
63
+ run: maturin build --release
64
+
65
+ - name: Check wheel
66
+ run: python -m twine check target/wheels/*.whl
@@ -0,0 +1,34 @@
1
+ # Rust build artifacts
2
+ target/
3
+ Cargo.lock
4
+
5
+ # Python build artifacts
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+
13
+ # maturin build output
14
+ *.so
15
+ *.pyd
16
+ *.dylib
17
+
18
+ # Virtual environments
19
+ .venv/
20
+ venv/
21
+ env/
22
+
23
+ # IDE
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ /research-doc
28
+
29
+ # proctrace runtime files
30
+ proctrace.log
31
+ /tmp/proctrace-*.pipe
32
+
33
+ # macOS
34
+ .DS_Store
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] — 2026-08-12
4
+
5
+ ### Added
6
+ - `proctrace.watch()` context manager: monitor memory, fd, thread, child process deltas
7
+ - `@proctrace.probe()` decorator: wraps sync and async functions
8
+ - `proctrace.install_signal_handler()`: SIGUSR1-triggered thread + asyncio dump
9
+ - `proctrace.trace_ipc(queue)`: latency and depth tracking for queues
10
+ - `proctrace.trace_pipe(r, w)`: latency tracking for pipe pairs
11
+ - `proctrace.trace_socket(sock)`: send latency + recv buffer utilization for sockets
12
+ - `proctrace.ipc_report()`: human-readable IPC channel summary
13
+ - `ProctraceLogger`: JSONL structured logging for ResourceDelta events
14
+ - `pytest-proctrace` plugin: per-test resource tracking via `--proctrace`
15
+ - `proctrace` CLI: `run`, `dump`, `watch` subcommands
16
+ - Linux backend: `/proc/self/status`, `/proc/self/fd`
17
+ - macOS backend: `proc_pidinfo(PROC_PIDTASKINFO)`, `fcntl(F_GETPATH)`
18
+ - Background Rust sampler thread for peak RSS tracking
19
+ - Zero runtime Python dependencies
20
+
21
+ ### Platform support
22
+ - Linux: ✓ (primary development platform)
23
+ - macOS: ✓ (tested on arm64 and x86_64)
24
+ - Windows: ✗ (no `/proc`, no `POSIX signals`)
@@ -0,0 +1,165 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "heck"
7
+ version = "0.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
10
+
11
+ [[package]]
12
+ name = "libc"
13
+ version = "0.2.189"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
16
+
17
+ [[package]]
18
+ name = "once_cell"
19
+ version = "1.21.4"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
22
+
23
+ [[package]]
24
+ name = "portable-atomic"
25
+ version = "1.15.0"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
28
+
29
+ [[package]]
30
+ name = "proc-macro2"
31
+ version = "1.0.107"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
34
+ dependencies = [
35
+ "unicode-ident",
36
+ ]
37
+
38
+ [[package]]
39
+ name = "pyo3"
40
+ version = "0.29.2"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
43
+ dependencies = [
44
+ "libc",
45
+ "once_cell",
46
+ "portable-atomic",
47
+ "pyo3-build-config",
48
+ "pyo3-ffi",
49
+ "pyo3-macros",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "pyo3-build-config"
54
+ version = "0.29.2"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
57
+ dependencies = [
58
+ "target-lexicon",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "pyo3-ffi"
63
+ version = "0.29.2"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
66
+ dependencies = [
67
+ "libc",
68
+ "pyo3-build-config",
69
+ ]
70
+
71
+ [[package]]
72
+ name = "pyo3-macros"
73
+ version = "0.29.2"
74
+ source = "registry+https://github.com/rust-lang/crates.io-index"
75
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
76
+ dependencies = [
77
+ "proc-macro2",
78
+ "pyo3-macros-backend",
79
+ "quote",
80
+ "syn 2.0.119",
81
+ ]
82
+
83
+ [[package]]
84
+ name = "pyo3-macros-backend"
85
+ version = "0.29.2"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
88
+ dependencies = [
89
+ "heck",
90
+ "proc-macro2",
91
+ "quote",
92
+ "syn 2.0.119",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "quote"
97
+ version = "1.0.47"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
100
+ dependencies = [
101
+ "proc-macro2",
102
+ ]
103
+
104
+ [[package]]
105
+ name = "syn"
106
+ version = "2.0.119"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
109
+ dependencies = [
110
+ "proc-macro2",
111
+ "quote",
112
+ "unicode-ident",
113
+ ]
114
+
115
+ [[package]]
116
+ name = "syn"
117
+ version = "3.0.3"
118
+ source = "registry+https://github.com/rust-lang/crates.io-index"
119
+ checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
120
+ dependencies = [
121
+ "proc-macro2",
122
+ "quote",
123
+ "unicode-ident",
124
+ ]
125
+
126
+ [[package]]
127
+ name = "tap"
128
+ version = "0.1.0"
129
+ dependencies = [
130
+ "libc",
131
+ "pyo3",
132
+ "thiserror",
133
+ ]
134
+
135
+ [[package]]
136
+ name = "target-lexicon"
137
+ version = "0.13.5"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
140
+
141
+ [[package]]
142
+ name = "thiserror"
143
+ version = "2.0.20"
144
+ source = "registry+https://github.com/rust-lang/crates.io-index"
145
+ checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
146
+ dependencies = [
147
+ "thiserror-impl",
148
+ ]
149
+
150
+ [[package]]
151
+ name = "thiserror-impl"
152
+ version = "2.0.20"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
155
+ dependencies = [
156
+ "proc-macro2",
157
+ "quote",
158
+ "syn 3.0.3",
159
+ ]
160
+
161
+ [[package]]
162
+ name = "unicode-ident"
163
+ version = "1.0.24"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "tap"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ authors = ["arif39x,aliarif1168@gmail.com"]
6
+ description = "Process introspection for humans."
7
+ license = "MIT"
8
+ readme = "README.md"
9
+
10
+ [lib]
11
+ name = "_proctrace_core"
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ libc = "0.2"
16
+ pyo3 = { version = "0.29", features = ["extension-module"] }
17
+ thiserror = "2"
18
+
19
+ [profile.release]
20
+ lto = true
21
+ opt-level = 3
22
+ codegen-units = 1
@@ -0,0 +1,323 @@
1
+ # proctrace — Developer Guide
2
+
3
+ > Non-invasive process and OS-state introspection for Python.
4
+ > Diagnose memory leaks, FD leaks, race conditions, and IPC bottlenecks with a single import.
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Architecture](#architecture)
11
+ 2. [How to Use proctrace](#how-to-use-proctrace)
12
+ - [Installation](#installation)
13
+ - [Context Manager (watch)](#context-manager-watch)
14
+ - [Decorator (@probe)](#decorator-probe)
15
+ - [IPC Tracing](#ipc-tracing)
16
+ - [On-demand Stack Dumps](#on-demand-stack-dumps)
17
+ - [CLI](#cli)
18
+ 3. [API Reference](#api-reference)
19
+ 4. [Code Snippets](#code-snippets)
20
+
21
+ ---
22
+
23
+ ## Architecture
24
+
25
+ proctrace is a **two-layer library**: a compiled Rust extension provides the low-level OS sampling, and a pure-Python layer offers the ergonomic API surface developers actually interact with.
26
+
27
+ ```
28
+ ┌─────────────────────────────────────────────────────────────────┐
29
+ │ Your Python Code │
30
+ └─────────────────────────┬───────────────────────────────────────┘
31
+ │ import proctrace
32
+ ┌─────────────────────────▼───────────────────────────────────────┐
33
+ │ Python Public API Layer │
34
+ │ │
35
+ │ watch() / ResourceWatcher @probe decorator │
36
+ │ trace_ipc / trace_pipe / trace_socket / ipc_report │
37
+ │ install_signal_handler() │
38
+ │ │
39
+ │ proctrace/__init__.py │
40
+ │ proctrace/watch.py proctrace/decorators.py │
41
+ │ proctrace/ipc.py proctrace/snapshot.py │
42
+ │ proctrace/_types.py (ResourceDelta dataclass) │
43
+ └─────────────────────────┬───────────────────────────────────────┘
44
+ │ PyO3 FFI boundary
45
+ ┌─────────────────────────▼───────────────────────────────────────┐
46
+ │ Rust Core Extension │
47
+ │ (_proctrace_core.cpython-*.so) │
48
+ │ │
49
+ │ resources.rs — snapshot_resources(), list_open_fds() │
50
+ │ sampler.rs — BackgroundSampler (peak RSS thread) │
51
+ │ ipc_probe.rs — IpcStats, SocketStats │
52
+ │ signal.rs — register_signal_pipe() │
53
+ │ lib.rs — PyO3 module root │
54
+ └─────────────────────────────────────────────────────────────────┘
55
+ │
56
+ Linux /proc
57
+ (macOS sysctl)
58
+ ```
59
+
60
+ ### Component Breakdown
61
+
62
+ | Component | File | Responsibility |
63
+ |---|---|---|
64
+ | `ResourceWatcher` | `watch.py` | Context manager; snapshots resources before/after a block; runs `BackgroundSampler` in the background |
65
+ | `BackgroundSampler` | `sampler.rs` | Rust thread that polls RSS on a configurable interval to capture the **peak** |
66
+ | `ResourceDelta` | `_types.py` | Pure-Python dataclass holding all measurements; serialises to/from dict/JSON |
67
+ | `@probe` decorator | `decorators.py` | Wraps sync and async functions with a `ResourceWatcher`; optionally stores result on `fn.last_probe_result` |
68
+ | IPC tracers | `ipc.py` | Thin wrappers around `queue.Queue`, OS pipes, and `socket` that record latency and throughput via `IpcStats`/`SocketStats` |
69
+ | Signal handler | `snapshot.py` | Installs a self-pipe trick via `register_signal_pipe()` (Rust); a daemon thread reads it and dumps all Python thread stacks + asyncio tasks |
70
+ | CLI | `cli.py` | `proctrace run / watch / dump` subcommands |
71
+ | Rust extension | `src/` | Compiled `.so`; exposes `snapshot_resources`, `list_open_fds`, `BackgroundSampler`, `IpcStats`, `SocketStats`, `register_signal_pipe`, `probe_version` |
72
+
73
+ ### Data Flow — `watch()` block
74
+
75
+ ```
76
+ __enter__
77
+ │
78
+ ├─ snapshot_resources() ← Rust: reads /proc/self/status
79
+ ├─ list_open_fds() ← Rust: reads /proc/self/fd/
80
+ ├─ threading.enumerate() ← Python stdlib
81
+ ├─ _count_children() ← /proc/.../children or pgrep
82
+ └─ BackgroundSampler.start() ← Rust thread spawned
83
+
84
+ [ your code runs ]
85
+
86
+ __exit__
87
+ │
88
+ ├─ BackgroundSampler.stop() → returns peak_rss_bytes
89
+ ├─ snapshot_resources() ← after snapshot
90
+ ├─ list_open_fds() ← after FD list
91
+ ├─ threading.enumerate() ← after thread list
92
+ └─ ResourceDelta(...) ← computed and stored on .result
93
+ ```
94
+
95
+ ---
96
+
97
+ ## How to Use proctrace
98
+
99
+ ### Installation
100
+
101
+ ```bash
102
+ pip install proctrace
103
+ ```
104
+
105
+ Build from source (requires Rust + `maturin`):
106
+
107
+ ```bash
108
+ pip install maturin
109
+ maturin develop --release
110
+ ```
111
+
112
+ **Requirements:** Python >= 3.10 · Linux or macOS
113
+
114
+ ---
115
+
116
+ ### Context Manager (`watch`)
117
+
118
+ The simplest entry point. Wrap any block to measure its resource footprint.
119
+
120
+ ```python
121
+ import proctrace
122
+
123
+ with proctrace.watch() as probe:
124
+ do_work()
125
+
126
+ print(probe.result.report())
127
+ ```
128
+
129
+ **Parameters of `proctrace.watch()`:**
130
+
131
+ | Parameter | Type | Default | Description |
132
+ |---|---|---|---|
133
+ | `memory` | `bool` | `True` | Track RSS and VMS memory |
134
+ | `fds` | `bool` | `True` | Track open file descriptors |
135
+ | `threads` | `bool` | `True` | Track thread count and names |
136
+ | `children` | `bool` | `True` | Track child process count |
137
+ | `sample_interval` | `float` | `0.1` | Background peak-RSS poll interval (seconds) |
138
+
139
+ After the `with` block, `probe.result` is a `ResourceDelta`.
140
+
141
+ ---
142
+
143
+ ### Decorator (`@probe`)
144
+
145
+ Attach resource tracking to any function — sync or async — without changing its call site.
146
+
147
+ ```python
148
+ from proctrace.decorators import probe
149
+
150
+ @probe(memory=True, fds=True, output="stderr")
151
+ def load_dataset(path: str) -> list:
152
+ ...
153
+
154
+ load_dataset("data.csv")
155
+
156
+ # Access the result afterwards
157
+ delta = load_dataset.last_probe_result
158
+ print(f"RSS grew by {delta.rss_delta_mb:.2f} MB")
159
+ ```
160
+
161
+ **Parameters of `@probe()`:**
162
+
163
+ | Parameter | Type | Default | Description |
164
+ |---|---|---|---|
165
+ | `memory` | `bool` | `True` | Track RSS / VMS |
166
+ | `fds` | `bool` | `True` | Track file descriptors |
167
+ | `threads` | `bool` | `True` | Track threads |
168
+ | `children` | `bool` | `False` | Track child processes |
169
+ | `sample_interval` | `float` | `0.05` | Background sampler interval |
170
+ | `output` | `"stderr"` or `"none"` | `"stderr"` | Print report automatically |
171
+ | `store` | `bool` | `True` | Store result on `fn.last_probe_result` |
172
+
173
+ Works identically on `async def` functions.
174
+
175
+ ---
176
+
177
+ ### IPC Tracing
178
+
179
+ Wrap queues, pipes, and sockets to collect latency and throughput metrics.
180
+
181
+ ```python
182
+ import queue, os, socket
183
+ from proctrace.ipc import trace_ipc, trace_pipe, trace_socket, ipc_report
184
+
185
+ # Queues
186
+ q = trace_ipc(queue.Queue(), name="task-queue")
187
+ q.put(item)
188
+ item = q.get()
189
+
190
+ # OS pipes
191
+ r_fd, w_fd = os.pipe()
192
+ pipe = trace_pipe(r_fd, w_fd, name="my-pipe")
193
+ pipe.write(b"hello")
194
+ data = pipe.read(5)
195
+
196
+ # Sockets
197
+ raw_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
198
+ sock = trace_socket(raw_sock, name="worker-conn")
199
+ sock.sendall(b"ping")
200
+ resp = sock.recv(1024)
201
+
202
+ # Print aggregated report for all traced channels
203
+ print(ipc_report())
204
+ ```
205
+
206
+ All traced wrappers are transparent — they support the same interface as their underlying object.
207
+
208
+ ---
209
+
210
+ ### On-demand Stack Dumps
211
+
212
+ Install a signal handler so you can trigger a full thread + asyncio task dump at any time without stopping the process.
213
+
214
+ ```python
215
+ import proctrace
216
+
217
+ proctrace.install_signal_handler(sig="SIGUSR1")
218
+
219
+ # From another terminal:
220
+ # kill -USR1 <pid>
221
+ ```
222
+
223
+ **Parameters of `install_signal_handler()`:**
224
+
225
+ | Parameter | Type | Default | Description |
226
+ |---|---|---|---|
227
+ | `sig` | `str` | `"SIGUSR1"` | Signal to listen for (`SIGUSR1`, `SIGUSR2`, `SIGALRM`) |
228
+ | `output` | `str` or `IO` | `"stderr"` | `"stderr"`, a file path string, or a file-like object |
229
+ | `include_asyncio` | `bool` | `True` | Also dump asyncio tasks |
230
+ | `loop` | `EventLoop or None` | `None` | Specific loop to inspect; auto-detected if `None` |
231
+
232
+ The dump includes a timestamped header, each thread's last 10 stack frames, and up to 5 frames per asyncio task.
233
+
234
+ ---
235
+
236
+ ### CLI
237
+
238
+ ```
239
+ proctrace <subcommand> [options]
240
+ ```
241
+
242
+ #### `proctrace run` — measure a subprocess
243
+
244
+ ```bash
245
+ proctrace run -- python myscript.py
246
+ proctrace run --json -- python myscript.py # JSON to stdout
247
+ ```
248
+
249
+ #### `proctrace watch` — live memory polling
250
+
251
+ ```bash
252
+ proctrace watch --pid <PID> --interval 1.0 --duration 30
253
+ ```
254
+
255
+ Prints a live-updating table of RSS and VMS (MB) for the target process.
256
+
257
+ #### `proctrace dump` — trigger a stack dump
258
+
259
+ ```bash
260
+ proctrace dump --pid <PID> --signal SIGUSR1
261
+ ```
262
+
263
+ Sends a signal to a running proctrace-instrumented process to trigger a thread dump on its stderr.
264
+
265
+ ---
266
+
267
+ ## API Reference
268
+
269
+ ### `ResourceDelta`
270
+
271
+ A dataclass produced after every measurement. All fields reflect the **delta** between entry and exit of the watched block.
272
+
273
+ | Attribute | Type | Description |
274
+ |---|---|---|
275
+ | `rss_delta_bytes` | `int` | RSS change in bytes (can be negative) |
276
+ | `vms_delta_bytes` | `int` | Virtual memory size change |
277
+ | `fd_delta` | `int` | Open file descriptor count change |
278
+ | `peak_rss_bytes` | `int` | Highest RSS observed by the background sampler |
279
+ | `thread_delta` | `int` | Thread count change |
280
+ | `child_delta` | `int` | Child process count change |
281
+ | `elapsed_ns` | `int` | Wall-clock time of block in nanoseconds |
282
+ | `leaked_fds` | `list[str]` | FD paths open at exit but not at entry |
283
+ | `new_thread_names` | `list[str]` | Names of threads created during the block |
284
+
285
+ **Convenience properties:**
286
+
287
+ | Property | Returns |
288
+ |---|---|
289
+ | `.rss_delta_mb` | `float` — RSS delta in megabytes |
290
+ | `.vms_delta_mb` | `float` — VMS delta in megabytes |
291
+ | `.peak_rss_mb` | `float` — Peak RSS in megabytes |
292
+ | `.elapsed_ms` | `float` — Elapsed time in milliseconds |
293
+
294
+ **Serialization:**
295
+
296
+ ```python
297
+ d = delta.to_dict() # -> dict[str, Any]
298
+ j = delta.to_json(indent=2) # -> JSON string
299
+ copy = ResourceDelta.from_dict(d)
300
+ ```
301
+
302
+ **Formatted report:**
303
+
304
+ ```python
305
+ print(delta.report()) # colour-coded when stderr is a TTY
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Code Snippets
311
+
312
+ See the [`guide-codesnippet/`](./guide-codesnippet/) folder for runnable examples:
313
+
314
+ | File | What it demonstrates |
315
+ |---|---|
316
+ | [`01_basic_watch.py`](./guide-codesnippet/01_basic_watch.py) | Minimal `watch()` context manager |
317
+ | [`02_selective_tracking.py`](./guide-codesnippet/02_selective_tracking.py) | Enabling only specific metrics |
318
+ | [`03_probe_decorator.py`](./guide-codesnippet/03_probe_decorator.py) | `@probe` on sync and async functions |
319
+ | [`04_json_export.py`](./guide-codesnippet/04_json_export.py) | Serialising results to JSON / dict |
320
+ | [`05_fd_leak_detection.py`](./guide-codesnippet/05_fd_leak_detection.py) | Detecting file descriptor leaks |
321
+ | [`06_ipc_tracing.py`](./guide-codesnippet/06_ipc_tracing.py) | Tracing queues, pipes, and sockets |
322
+ | [`07_signal_dump.py`](./guide-codesnippet/07_signal_dump.py) | On-demand thread + asyncio dump via signal |
323
+ | [`08_cli_usage.sh`](./guide-codesnippet/08_cli_usage.sh) | CLI `run`, `watch`, and `dump` commands |