feat(profiling): add GC observability collector - #19940
Conversation
Adds GCCollector that hooks gc.callbacks to measure per-generation pause durations, emits alloc samples for collected objects, and records explicit gc.collect() call counts per flush interval. Controlled via DD_PROFILING_GC_ENABLED (default: true).
|
|
@codex review |
Codeowners resolved asResolved from the full PR diff against |
Circular import analysis
|
Dependency direction analysis
|
There was a problem hiding this comment.
Pull request overview
Adds a new profiling “GC observability” collector that hooks gc.callbacks to emit profiling samples around GC pauses, plus a new configuration flag (DD_PROFILING_GC_ENABLED) to control enablement, and wires the collector into the main Profiler.
Changes:
- Introduces
GCCollectorthat patchesgc.collect, subscribes togc.callbacks, emits wall-time samples for GC pauses, and emits a periodic “gc.config” snapshot sample. - Adds
DD_PROFILING_GC_ENABLEDto profiling settings and supported configuration registry, and enables the collector inddtrace.profiling.profiler.Profiler. - Adds unit + integration tests validating callback lifecycle and that GC samples appear in exported profiles.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/profiling/collector/test_gc.py | New unit/integration tests for GCCollector behavior and emitted samples. |
| supported-configurations.json | Registers new DD_PROFILING_GC_ENABLED configuration key. |
| ddtrace/profiling/profiler.py | Wires GCCollector into the profiler collector initialization flow. |
| ddtrace/profiling/collector/gc.py | New collector implementation using gc.callbacks + ddup.SampleHandle. |
| ddtrace/internal/settings/profiling.py | Adds ProfilingConfigGC (profiling_config.gc.enabled). |
| ddtrace/internal/settings/_supported_configurations.py | Updates generated allowlist to include DD_PROFILING_GC_ENABLED. |
Suppressed comments (1)
ddtrace/profiling/collector/gc.py:79
- push_alloc(size, count) treats the first argument as alloc-space (bytes) and the second as alloc-samples. Passing the number of collected objects as the size and a constant count of 1 will misrepresent the data in the allocation profile (object count will appear as bytes).
collected = info.get("collected", 0)
if collected > 0:
handle2 = ddup.SampleHandle()
handle2.push_alloc(collected, 1)
handle2.push_frame(frame_name, "gc", 0, gen)
handle2.push_monotonic_ns(time.monotonic_ns())
handle2.flush_sample()
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fff59f9fc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ngs reload Config-sample uniqueness raced with gc.callbacks walltime, and importlib.reload replaced ProfilingConfigHeap so later isinstance() checks failed.
Restore gc.collect only when our wrapper is still installed, drain the explicit-call counter under a lock, and reset in-flight state after fork. Align help/docstrings with emitted samples and add a release note.
Keep the new GC pause collector off until customers set DD_PROFILING_GC_ENABLED=true.
| { | ||
| "implementation": "C", | ||
| "type": "boolean", | ||
| "default": "true" | ||
| } |
| # TODO: If we later want collected-object counts on the profile, add a | ||
| # dedicated libdatadog/pprof sample type. Do not reuse push_alloc. |
| handle: ddup.SampleHandle = ddup.SampleHandle() | ||
| handle.push_walltime(pause_ns, 1) | ||
| handle.push_frame(frame_name, "gc", 0, gen) | ||
| handle.push_monotonic_ns(time.monotonic_ns()) | ||
| handle.flush_sample() |
Adds GCCollector that hooks gc.callbacks to measure per-generation pause durations, emits alloc samples for collected objects, and records explicit gc.collect() call counts per flush interval. Controlled via DD_PROFILING_GC_ENABLED (default: true).
Description
Testing
Risks
Additional Notes