gcmon 0.2.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 (47) hide show
  1. gcmon-0.2.0/LICENSE +21 -0
  2. gcmon-0.2.0/PKG-INFO +393 -0
  3. gcmon-0.2.0/README.md +364 -0
  4. gcmon-0.2.0/pyproject.toml +131 -0
  5. gcmon-0.2.0/src/gcmon/__init__.py +19 -0
  6. gcmon-0.2.0/src/gcmon/__main__.py +6 -0
  7. gcmon-0.2.0/src/gcmon/_env.py +223 -0
  8. gcmon-0.2.0/src/gcmon/child_process_runner.py +237 -0
  9. gcmon-0.2.0/src/gcmon/cli.py +127 -0
  10. gcmon-0.2.0/src/gcmon/commands/__init__.py +9 -0
  11. gcmon-0.2.0/src/gcmon/commands/convert_cmd.py +111 -0
  12. gcmon-0.2.0/src/gcmon/commands/monitor_cmd.py +57 -0
  13. gcmon-0.2.0/src/gcmon/commands/monitoring_base.py +76 -0
  14. gcmon-0.2.0/src/gcmon/commands/monitoring_options.py +176 -0
  15. gcmon-0.2.0/src/gcmon/commands/parser_factory.py +6 -0
  16. gcmon-0.2.0/src/gcmon/commands/run_cmd.py +92 -0
  17. gcmon-0.2.0/src/gcmon/control/__init__.py +0 -0
  18. gcmon-0.2.0/src/gcmon/control/control_client.py +116 -0
  19. gcmon-0.2.0/src/gcmon/control/control_server.py +286 -0
  20. gcmon-0.2.0/src/gcmon/data.py +60 -0
  21. gcmon-0.2.0/src/gcmon/exporters/__init__.py +27 -0
  22. gcmon-0.2.0/src/gcmon/exporters/chrome_trace_exporter.py +131 -0
  23. gcmon-0.2.0/src/gcmon/exporters/chrome_trace_format.py +400 -0
  24. gcmon-0.2.0/src/gcmon/exporters/chrome_trace_io.py +236 -0
  25. gcmon-0.2.0/src/gcmon/exporters/exporter.py +32 -0
  26. gcmon-0.2.0/src/gcmon/exporters/exporter_factory.py +27 -0
  27. gcmon-0.2.0/src/gcmon/exporters/jsonl_exporter.py +103 -0
  28. gcmon-0.2.0/src/gcmon/exporters/perfetto_exporter.py +136 -0
  29. gcmon-0.2.0/src/gcmon/exporters/perfetto_format.py +582 -0
  30. gcmon-0.2.0/src/gcmon/exporters/protobuf_encoder.py +70 -0
  31. gcmon-0.2.0/src/gcmon/exporters/stdout_exporter.py +39 -0
  32. gcmon-0.2.0/src/gcmon/monitor.py +125 -0
  33. gcmon-0.2.0/src/gcmon/monitor_loop.py +60 -0
  34. gcmon-0.2.0/src/gcmon/monitor_thread.py +133 -0
  35. gcmon-0.2.0/src/gcmon/poll_status.py +9 -0
  36. gcmon-0.2.0/src/gcmon/protocol.py +190 -0
  37. gcmon-0.2.0/src/gcmon/pyperf/__init__.py +11 -0
  38. gcmon-0.2.0/src/gcmon/pyperf/hook.py +295 -0
  39. gcmon-0.2.0/src/gcmon/run_policy.py +36 -0
  40. gcmon-0.2.0/src/gcmon/stats.py +280 -0
  41. gcmon-0.2.0/src/gcmon/stats_output.py +113 -0
  42. gcmon-0.2.0/src/gcmon/target_process.py +41 -0
  43. gcmon-0.2.0/src/gcmon/utils/__init__.py +10 -0
  44. gcmon-0.2.0/src/gcmon/utils/process_terminator.py +200 -0
  45. gcmon-0.2.0/src/gcmon/utils/replace_signals.py +20 -0
  46. gcmon-0.2.0/src/gcmon/utils/set_on_exit.py +13 -0
  47. gcmon-0.2.0/src/gcmon/wait_policy.py +55 -0
gcmon-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey Miryanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gcmon-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,393 @@
1
+ Metadata-Version: 2.4
2
+ Name: gcmon
3
+ Version: 0.2.0
4
+ Summary: GC Monitor
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: gc,garbage-collection,monitoring,perfetto,profiling
8
+ Author: Sergey Miryanov
9
+ Author-email: sergey.miryanov@gmail.com
10
+ Requires-Python: >=3.15
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Classifier: Topic :: Software Development :: Debuggers
17
+ Classifier: Topic :: System :: Monitoring
18
+ Provides-Extra: cmdline
19
+ Provides-Extra: stats
20
+ Requires-Dist: ddsketch (>=3.0.1,<4.0.0) ; extra == "stats"
21
+ Requires-Dist: msgspec (==0.21.1)
22
+ Requires-Dist: psutil (>=7.2) ; extra == "cmdline"
23
+ Project-URL: Documentation, https://github.com/sergey-miryanov/gcmon#readme
24
+ Project-URL: Homepage, https://github.com/sergey-miryanov/gcmon
25
+ Project-URL: Issues, https://github.com/sergey-miryanov/gcmon/issues
26
+ Project-URL: Repository, https://github.com/sergey-miryanov/gcmon
27
+ Description-Content-Type: text/markdown
28
+
29
+ # gcmon - zero-overhead GC monitoring for Python
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/gcmon.svg)](https://pypi.org/project/gcmon/)
32
+ [![CI](https://github.com/sergey-miryanov/gcmon/actions/workflows/ci.yml/badge.svg)](https://github.com/sergey-miryanov/gcmon/actions/workflows/ci.yml)
33
+ [![Python Version](https://img.shields.io/badge/python-3.15+-blue.svg)](https://pypi.org/project/gcmon/)
34
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
35
+
36
+ A package for monitoring Python garbage collection events and exporting
37
+ statistics in various formats.
38
+
39
+ ## Why gcmon?
40
+
41
+ Python's garbage collector can introduce unpredictable pauses in
42
+ applications. The standard library provides `gc.get_stats()` for
43
+ aggregate collection counters and `gc.callbacks` for per-event hooks,
44
+ but both run inside the target process: callbacks add execution
45
+ overhead that distorts timing, while `gc.get_stats()` only exposes
46
+ cumulative counters with no per-pause resolution. Neither can monitor
47
+ a process without modifying its code.
48
+
49
+ gcmon reads GC statistics directly from a target process's memory via
50
+ the `_remote_debugging` CPython extension, with zero in-process
51
+ overhead and without pausing the target process — no code changes or
52
+ runtime modification required.
53
+
54
+ Use it to profile GC pause times in production services, debug memory
55
+ leaks, or integrate GC metrics into benchmarks.
56
+
57
+ ## Features
58
+
59
+ - **Real-time GC monitoring** - Track garbage collection events in running Python
60
+ processes without in-process overhead
61
+ - **Multiple export formats** - Chrome Trace Event, JSONL file, and JSONL to stdout
62
+ - **CLI** - Monitor processes or run scripts with GC monitoring
63
+ - **Pyperf hook integration** - Seamlessly integrate with pyperf benchmarks
64
+
65
+ ## Alternatives Comparison
66
+
67
+ | Approach | In-process? | Per-pause resolution | Zero code change | Overhead |
68
+ |---|---|---|---|---|
69
+ | `gc.callbacks` | Yes | Yes | No | No — distorts timing |
70
+ | `gc.get_stats()` | Yes | No — cumulative only | No | Minimal |
71
+ | `tracemalloc` | Yes | N/A — allocations, not GC | No | High |
72
+ | [`memray`](https://github.com/bloomberg/memray) | Yes (partial¹) | N/A — allocations, not GC | Partial (attach mode) | Moderate |
73
+ | [`py-spy`](https://github.com/benfred/py-spy) | No | N/A — CPU profiling only | Yes | Low |
74
+ | [`austin`](https://github.com/P403n1x87/austin) | No | Partial² | Yes | Minimal |
75
+ | **gcmon** | **No** | **Yes** | **Yes** | **Yes — zero in-process cost** |
76
+
77
+ ¹ memray's `attach` mode avoids modifying code but still injects an allocator into the target process.
78
+ ² austin's `-g` flag tags frames during GC activity but provides no per-pause timing or heap data.
79
+
80
+ ## How It Works
81
+
82
+ gcmon runs **outside** the target process. It reads GC statistics directly
83
+ from the process's memory via the `_remote_debugging` CPython C extension
84
+ (available in CPython 3.15+), which uses platform-specific memory access APIs.
85
+
86
+ For the pyperf hook integration, gcmon uses an **external process model**:
87
+
88
+ 1. The hook spawns the `gcmon` CLI as a separate process
89
+ 2. The external process reads the target process memory directly
90
+ 3. Results are written to a temporary JSON file
91
+ 4. The hook reads the JSON and injects metrics into pyperf metadata
92
+
93
+ This provides zero in-process overhead during benchmarks, crash isolation
94
+ (gcmon crashes don't affect the target), and clean separation of concerns.
95
+
96
+ ## Limitations
97
+
98
+ The monitoring and monitored processes must use the **exact same Python version
99
+ and build**. `gcmon` reads GC statistics directly from the target process's
100
+ in-memory data structures. The layout of these structures varies between Python
101
+ versions (fields, offsets, sizes), so mismatched binaries are rejected by
102
+ `_remote_debugging` to prevent undefined behavior or crashes.
103
+
104
+ ## Installation
105
+
106
+ ```bash
107
+ pip install gcmon
108
+
109
+ # With stats support (see Statistics below)
110
+ pip install gcmon[stats]
111
+ ```
112
+
113
+ ## Quick Start
114
+
115
+ ```bash
116
+ # Monitor a running process by PID (default Chrome Trace format)
117
+ gcmon 12345
118
+
119
+ # Run a Python script with GC monitoring
120
+ gcmon run -s my_script.py
121
+
122
+ # Monitor with custom output and statistics output
123
+ gcmon 12345 -o trace.json --stats
124
+ ```
125
+
126
+ ### Example: Chrome Trace Output
127
+
128
+ <img src="docs/images/chrome-trace-example.png" alt="Chrome Trace Example" width="800">
129
+
130
+ *Example: GC monitoring data visualized in Chrome Trace viewer showing:*
131
+ - *GC Pause events (top row with markers)*
132
+ - *Heap Size over time (green area chart)*
133
+ - *Memory Counters*
134
+
135
+ This visualization helps you:
136
+ - **Identify GC pause patterns** - See when and how long GC pauses occur
137
+ - **Track memory growth** - Monitor heap size changes over time
138
+ - **Analyze collection efficiency** - Compare GC-related counters
139
+ - **Debug memory issues** - Spot memory leaks or inefficient collection patterns
140
+
141
+ ### Example: JSONL Output
142
+
143
+ With `--format jsonl` (writes to file) or `--format stdout` (writes to terminal),
144
+ each line is a JSON object representing one GC event:
145
+
146
+ ```jsonl
147
+ {"pid": 12345, "tid": 0, "gen": 0, "iid": 1, "ts_start": 1700000000000000, "ts_stop": 1700000000001500, "heap_size": 1048576, "collections": 42, "collected": 120, "uncollectable": 0, "candidates": 300, "duration": 1.5}
148
+ {"pid": 12345, "tid": 0, "gen": 1, "iid": 2, "ts_start": 1700000000200000, "ts_stop": 1700000000235000, "heap_size": 2097152, "collections": 3, "collected": 85, "uncollectable": 1, "candidates": 150, "duration": 3.5}
149
+ ```
150
+
151
+ | Field | Description |
152
+ |-------|-------------|
153
+ | `pid` | Process ID of the monitored target |
154
+ | `gen` | GC generation (0, 1, or 2) |
155
+ | `iid` | Interpreter ID (`0` for the main interpreter) |
156
+ | `ts_start`, `ts_stop` | Event timestamps (nanoseconds) |
157
+ | `heap_size` | Heap size at event time (bytes) |
158
+ | `collections` | Cumulative collection count for this generation |
159
+ | `collected` | Objects collected in this event |
160
+ | `uncollectable` | Objects that could not be collected |
161
+ | `candidates` | Candidate objects for collection |
162
+ | `duration` | Pause duration (milliseconds) |
163
+
164
+ For incremental GC events, additional fields (`increment_size`, `alive_size`,
165
+ `ts_mark_alive_*`, `ts_fill_increment_*`, `ts_deduce_unreachable_*`) are included.
166
+
167
+ ## When to Use
168
+
169
+ **Use gcmon when you want to:**
170
+
171
+ - Profile GC pause times in production or staging without modifying application code
172
+ - Measure GC impact on latency-sensitive services (APIs, real-time systems)
173
+ - Correlate GC activity with benchmark results via the pyperf hook
174
+ - Track heap size trends over time across running processes
175
+ - Debug intermittent latency spikes suspected to be GC-related
176
+
177
+ **Use something else when you need to:**
178
+
179
+ - Per-object allocation tracking — use [`tracemalloc`](https://docs.python.org/3/library/tracemalloc.html)
180
+ - Object reference graph inspection — use [`objgraph`](https://pypi.org/project/objgraph/)
181
+ - Allocation profiling — use [`memray`](https://github.com/bloomberg/memray)
182
+ - CPU profiling and flame graphs — use [`py-spy`](https://github.com/benfred/py-spy) or [`austin`](https://github.com/P403n1x87/austin)
183
+ - Coarse GC activity tagging in CPU profiles — use [`austin`](https://github.com/P403n1x87/austin) with `-g` (no per-pause timing or heap data)
184
+ - In-process GC callbacks (e.g., triggering actions on collection) — use [`gc.callbacks`](https://docs.python.org/3/library/gc.html#gc.callbacks)
185
+ - Cumulative collection counters without per-pause detail — use [`gc.get_stats()`](https://docs.python.org/3/library/gc.html#gc.get_stats)
186
+ - Monitor across different Python builds — gcmon requires the exact same binary (see [Limitations](#limitations))
187
+
188
+ ## CLI Usage
189
+
190
+ The `gcmon` command uses subcommands (`monitor`, `run`). If no subcommand
191
+ is given, `monitor` is used by default.
192
+
193
+ ### monitor
194
+
195
+ Monitor a running process by PID.
196
+
197
+ ```bash
198
+ # Monitor a process until interrupted (Chrome format)
199
+ gcmon 12345
200
+ # or:
201
+ gcmon monitor 12345
202
+
203
+ # Monitor with custom output file
204
+ gcmon 12345 -o gc_trace.json
205
+
206
+ # Monitor for a specific duration with verbose output
207
+ gcmon 12345 -d 30 -v
208
+
209
+ # High-frequency monitoring
210
+ gcmon 12345 --output trace.json --rate 0.01
211
+ ```
212
+
213
+ ### run
214
+
215
+ Run a Python script or module with GC monitoring enabled.
216
+
217
+ **Important:** All options and arguments after `-s`/`--script` or `-m`/`--module` are passed verbatim to the target — they are **not** interpreted by gcmon. Place gcmon options before the target.
218
+
219
+ ```bash
220
+ # Run a script
221
+ gcmon run -s my_script.py
222
+
223
+ # Run a module (like python -m)
224
+ gcmon run --stats --table-format md -m test test_gc -v
225
+
226
+ # Pass arguments to the script; everything after -s goes to the target
227
+ gcmon run -s benchmark.py --iterations 1000 --verbose
228
+
229
+ # Run a module with GC monitoring options
230
+ gcmon run --format jsonl -o trace.jsonl --stats -m http.server 8000
231
+ ```
232
+
233
+ You must specify exactly one of `-s`/`--script` or `-m`/`--module`.
234
+
235
+ ### Options
236
+
237
+ | Option | Applies to | Description | Default |
238
+ |--------|------------|-------------|---------|
239
+ | `pid` (required) | `monitor` | Process ID to monitor | - |
240
+ | `-s, --script <path>` | `run` | Python script path to run | - |
241
+ | `-m, --module <name>` | `run` | Module name to run (like `python -m`) | - |
242
+ | `-o, --output` | both | Output file path for trace data | `gcmon.json` (chrome), `gcmon.jsonl` (JSONL) |
243
+ | `-r, --rate` | both | Polling rate in seconds | `0.1` |
244
+ | `-d, --duration` | both | Monitoring duration in seconds | Until interrupted / script exits |
245
+ | `-v, --verbose` | both | Enable verbose output (`-v` for INFO, `-vv` for DEBUG) | `0` |
246
+ | `--format` | both | Output format: `chrome` (Chrome Trace Event), `jsonl` (JSONL to file), or `stdout` (JSONL to stdout) | `chrome` |
247
+ | `--flush-threshold` | both | Number of events to buffer before flushing (JSONL format) | `100` |
248
+ | `--stats` | both | Show statistics table at end of monitoring (see [Statistics](#statistics)) | `False` |
249
+ | `--table-format` | both | Table format: `plain` or `markdown`/`md` | `plain` |
250
+
251
+ ### Environment Variables
252
+
253
+ All CLI options can be overridden via environment variables. CLI flags take precedence.
254
+
255
+ | Variable | Equivalent flag | Description | Default |
256
+ |----------|----------------|-------------|---------|
257
+ | `GCMON_OUTPUT` | `-o, --output` | Output file path for trace data | `gcmon.json` (chrome), `gcmon.jsonl` (JSONL) |
258
+ | `GCMON_RATE` | `-r, --rate` | Polling rate in seconds | `0.1` |
259
+ | `GCMON_DURATION` | `-d, --duration` | Monitoring duration in seconds | Until interrupted / script exits |
260
+ | `GCMON_VERBOSE` | `-v, --verbose` | Verbose level (integer or truthy value) | `0` |
261
+ | `GCMON_FORMAT` | `--format` | Output format: `chrome`, `jsonl`, or `stdout` | `chrome` |
262
+ | `GCMON_FLUSH_THRESHOLD` | `--flush-threshold` | Number of events to buffer before flushing (JSONL format) | `100` |
263
+ | `GCMON_STATS` | `--stats` | Enable statistics table (`1`, `true`, `yes`, `on`) | `False` |
264
+ | `GCMON_TABLE_FORMAT` | `--table-format` | Table format: `plain`, `md`, or `markdown` | `plain` |
265
+
266
+
267
+ ## Statistics
268
+
269
+ Use `--stats` to display a statistics table at the end of monitoring. The table reports GC pause durations (p50, p90, p95, p99) and counts per generation, broken down by process.
270
+
271
+ ### Example Output
272
+
273
+ ```bash
274
+ $ gcmon 12345 --stats --table-format md
275
+
276
+ | PID | Metric | Count | Sum | Avg | P50 | P90 | P95 | P99 |
277
+ |-------|------------------|-------|---------|---------|---------|---------|---------|---------|
278
+ | Total | GC Pause(0) | 42 | 35.200 | 0.838 | 0.720 | 1.500 | 1.800 | 2.400 |
279
+ | | GC Pause(1) | 18 | 72.000 | 4.000 | 3.500 | 6.800 | 7.500 | 10.200 |
280
+ | | GC Pause(2) | 5 | 125.000 | 25.000 | 22.000 | 38.000 | 42.000 | 50.000 |
281
+ | | | | | | | | | |
282
+ | 12345 | GC Pause(0) | 42 | 35.200 | 0.838 | 0.720 | 1.500 | 1.800 | 2.400 |
283
+ | | GC Pause(1) | 18 | 72.000 | 4.000 | 3.500 | 6.800 | 7.500 | 10.200 |
284
+ | | GC Pause(2) | 5 | 125.000 | 25.000 | 22.000 | 38.000 | 42.000 | 50.000 |
285
+ ```
286
+
287
+ *Values shown in milliseconds. Metrics are reported per GC generation (0, 1, 2).*
288
+
289
+ ### Without `[stats]` extra
290
+
291
+ By default, statistics are computed from an in-memory buffer of up to 1024 samples. Percentiles are calculated exactly by sorting the buffered values. Once the buffer is full, older samples are discarded. This is sufficient for short monitoring sessions but may lose data during long runs.
292
+
293
+ ### With `[stats]` extra
294
+
295
+ Install the optional `ddsketch` dependency for high-accuracy, memory-efficient statistics:
296
+
297
+ ```bash
298
+ pip install gcmon[stats]
299
+ ```
300
+
301
+ This installs [DDSketch](https://github.com/DataDog/sketches-py), which:
302
+ - Tracks **all** samples without a fixed buffer limit
303
+ - Computes approximate quantiles with 0.1% relative accuracy
304
+ - Uses constant memory regardless of monitoring duration
305
+
306
+ For long-running processes or high-frequency polling, the `[stats]` extra is recommended.
307
+
308
+
309
+ ## Pyperf Hook Integration
310
+
311
+ The gcmon package provides a pyperf hook for automatic GC metrics collection during benchmarks.
312
+
313
+ ### Usage
314
+
315
+ ```bash
316
+ # Run benchmark with GC monitoring
317
+ python my_benchmark.py --hook=gcmon
318
+
319
+ # Or using pyperf directly
320
+ pyperf timeit --hook=gcmon my_benchmark.py
321
+
322
+ # Save results with GC metrics
323
+ python my_benchmark.py --hook=gcmon -o benchmark_results.json
324
+
325
+ ```
326
+
327
+ ### GC Metrics Collected
328
+
329
+ The hook collects and reports the following GC metrics in pyperf metadata:
330
+
331
+ - `gc_pause_gen_0_p99`, `gc_pause_gen_1_p99`, `gc_pause_gen_2_p99` - P99 GC pause duration by generation (microseconds)
332
+ - `gc_pause_gen_0_sum`, `gc_pause_gen_1_sum`, `gc_pause_gen_2_sum` - Total GC pause time by generation (microseconds)
333
+ - `gc_pause_gen_0_count`, `gc_pause_gen_1_count`, `gc_pause_gen_2_count` - Number of GC pauses by generation
334
+ - `gc_heap_size_p99` - P99 heap size across all samples (bytes)
335
+
336
+ ## How It Works
337
+
338
+ For the pyperf hook integration, gcmon uses an **external process model**:
339
+
340
+ 1. The hook spawns the `gcmon` CLI as a separate process
341
+ 2. The external process reads the target process memory directly
342
+ 3. Results are written to a temporary JSONL file
343
+ 4. The hook reads the JSONL and injects metrics into pyperf metadata
344
+
345
+ This provides zero in-process overhead during benchmarks, crash isolation
346
+ (gcmon crashes don't affect the target), and clean separation of concerns.
347
+
348
+ ### Example: Perfetto Trace Viewer for Pyperf Benchmarks
349
+
350
+ When you run a pyperf benchmark with the gcmon hook and export the results in Chrome Trace format, you can visualize the GC activity alongside the benchmark execution in Perfetto:
351
+
352
+ <img src="docs/images/perfetto-pyperf-example.png" alt="Perfetto Pyperf Example" width="800">
353
+
354
+ *Example: Pyperf benchmark trace visualized in Perfetto showing:*
355
+ - *Multiple benchmark worker processes running in parallel*
356
+ - *GC Monitor process tracking memory events*
357
+ - *Timeline view of benchmark execution with GC activity*
358
+
359
+ This visualization helps you:
360
+ - **Correlate GC activity with benchmark performance** - See how GC pauses affect benchmark timing
361
+ - **Identify performance outliers** - Spot runs affected by GC pauses
362
+ - **Analyze parallel benchmark execution** - Monitor multiple worker processes simultaneously
363
+ - **Debug benchmark variability** - Understand sources of timing variation between runs
364
+
365
+ To generate traces for Perfetto:
366
+ ```bash
367
+ export GCMON_PYPERF_HOOK_OUTPUT="gcmon_{bench_name}.jsonl"
368
+ # Run benchmark with GC monitoring and JSONL output
369
+ python my_benchmark.py --hook=gcmon --inherit-environ=GCMON_PYPERF_HOOK_OUTPUT -p 5
370
+
371
+ # Open in Perfetto UI (https://ui.perfetto.dev)
372
+ ```
373
+
374
+ `--inherit-environ` is needed because pyperf isolates worker environments by default;
375
+ it tells pyperf to pass `GCMON_PYPERF_HOOK_OUTPUT` from the parent shell to
376
+ worker subprocesses so the hook writes to the intended file.
377
+
378
+ ### Environment Variables
379
+
380
+ | Variable | Description | Default |
381
+ |----------|-------------|---------|
382
+ | `GCMON_PYPERF_HOOK_OUTPUT` | Output path for the combined GC trace file (JSONL). Supports `{bench_name}` and `{pid}` substitution. | `gcmon_{bench_name}_combined_{pid}.jsonl` |
383
+ | `GCMON_PYPERF_HOOK_TEMP_DIR` | Directory for temporary JSONL files written during monitoring. | System temp directory |
384
+ | `GCMON_PYPERF_HOOK_VERBOSE` | Enable verbose logging from the hook. Accepts `1`, `yes`, `on`, or `true` (case-insensitive). | Disabled |
385
+
386
+ ## License
387
+
388
+ MIT License - see [LICENSE](LICENSE) for details.
389
+
390
+ ## Contributing
391
+
392
+ Bug reports and pull requests are welcome at [GitHub](https://github.com/sergey-miryanov/gcmon/issues).
393
+