feat(profiling): [NO MERGE][2/n] support Python 3.15 for profiling#17532
feat(profiling): [NO MERGE][2/n] support Python 3.15 for profiling#17532vlad-scherbich wants to merge 32 commits intovlad/ddtracepy-315-tracing-wrappingfrom
Conversation
d3934e3 to
6e57d4b
Compare
aa07e99 to
06e9017
Compare
f8bf205 to
5419900
Compare
4526b3b to
4d5a9de
Compare
4f37ced to
7513bb8
Compare
|
@chatgpt-codex-connector please review |
There was a problem hiding this comment.
Pull request overview
This PR extends dd-trace-py’s Continuous Profiler compatibility to Python 3.15 by updating native/Rust dependencies (notably pyo3) and adding new Echion/asyncio guardrails, along with CI/matrix expansions and new local compatibility tooling.
Changes:
- Add Python 3.15 support signals and matrices across packaging/CI/riot, plus release notes and local verification scripts.
- Update native components: pyo3 0.28 upgrade, libdatadog rev bump, and Echion frame/task handling adjustments for CPython 3.15 internals.
- Introduce new Echion contract/runtime tests and temporarily xfail a couple of profiling tests on 3.15 while issues are investigated.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/profiling/test_uwsgi.py | Marks a known 3.15 uwsgi wall-time sampling issue as xfail. |
| tests/profiling/test_scheduler.py | Avoids real uploads during a logging-focused scheduler test by patching ddup.upload. |
| tests/profiling/collector/test_asyncio_idle.py | Temporarily xfails asyncio idle frame-capture validation on 3.15. |
| src/native/data_pipeline/mod.rs | Updates TraceExporter usage to use NativeCapabilities-parameterized exporter type. |
| src/native/Cargo.toml | Bumps pyo3/pyo3-ffi/pyo3-build-config to 0.28 and adds libdd-capabilities-impl; updates libdatadog git rev. |
| src/native/Cargo.lock | Lockfile updates reflecting pyo3/libdatadog dependency changes. |
| setup.py | Adds a 3.15 guard to set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 as a build safety net. |
| scripts/verify_profiler_compatibility.py | Adds a profiler compatibility verification tool with baseline compare support. |
| scripts/run-profiling-tests | Adds a one-command runner to rebuild/check stack extension and run profiling suites for a target Python. |
| scripts/profiles/compatibility_baselines.json | Adds initial baselines for compatibility verification across 3.9–3.15. |
| riotfile.py | Adds Python 3.15 to supported matrices and sets pyo3 forward-compat env for builds. |
| releasenotes/notes/profiling-python315-support-5910a6a4e623716b.yaml | Announces Python 3.15 profiler support. |
| pyproject.toml | Extends supported Python range to <3.16 and adds the 3.15 classifier. |
| ddtrace/profiling/_asyncio.py | Adds guards/warnings for private asyncio internals and future-version graceful degradation. |
| ddtrace/internal/datadog/profiling/stack/test/test_frame_state_315.cpp | Adds 3.15-specific frame-state tests around PyGen_yf. |
| ddtrace/internal/datadog/profiling/stack/test/test_cpython_layout_contracts.cpp | Adds compile-time/static enum contracts for CPython internals Echion depends on. |
| ddtrace/internal/datadog/profiling/stack/test/CMakeLists.txt | Registers the new Echion tests in the CMake test target list. |
| ddtrace/internal/datadog/profiling/stack/src/echion/frame.cc | Switch-based handling for _frameowner changes (incl. 3.15 CSTACK removal), aiming to catch new enum values at build time. |
| ddtrace/internal/datadog/profiling/stack/echion/echion/cpython/tasks.h | Adds a 3.15-specific PyGen_yf implementation handling the new locked yield-from state (nogil builds). |
| ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp | Ensures Python.h is included first to avoid _POSIX_C_SOURCE/_XOPEN_SOURCE redefinition warnings-as-errors on 3.15+. |
| .gitlab/testrunner.yml | Adds 3.15 to the pyenv global list used in CI. |
| .gitlab/package.yml | Adds 3.15 to package build/test matrices and tags. |
| .gitlab/multi-os-tests.yml | Adds 3.15 to multi-OS test coverage. |
| .gitlab-ci.yml | Adds 3.15 to profiling_native matrices (sanitizers/valgrind/etc.). |
| .github/workflows/generate-supported-versions.yml | Adds Python 3.15 setup to version generation workflow. |
| .github/workflows/generate-package-versions.yml | Adds Python 3.15 setup to package-version generation workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7513bb8616
ℹ️ 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".
43e778a to
2eae691
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
444f513 to
4fe3e69
Compare
CPython 3.15 changed two bytecode operands in async/generator code paths: - YIELD_VALUE in SEND loops (await): 0 → 1 - RESUME after async gen yield-to-caller: 1 → 5 asyncs.py: new 3.15 branch with updated operands; ASYNC_HEAD and COROUTINE_ASSEMBLY structure identical to 3.14, ASYNC_GEN_ASSEMBLY yield_value updated in all three await loops (presend0/1/2) and resume updated in the yield-to-caller section. generators.py: new 3.15 branch identical to 3.14 — generator yields inside try blocks still use RESUME 1 in 3.15 (only unprotected yields changed to RESUME 5). context.py: extended 3.13 branch to cover 3.15 — no bytecode changes needed for context enter/exit/return assembly. All version guards bumped from >= (3,15) to >= (3,16). Validated on Python 3.15.0a7: coroutine, generator, async generator, and generator throw all pass.
The 3.13 INJECTION_ASSEMBLY (load_const/push_null/call/pop_top) is valid on 3.15 — no call convention changes were made. Bump the NotImplementedError guard from 3.15 to 3.16.
CPython 3.15 made two breaking internal ABI changes in the profiler hot path: 1. PyFrameState enum completely renumbered (FRAME_EXECUTING: 0 to 4, etc.) and FRAME_SUSPENDED_YIELD_FROM_LOCKED added for free-threaded builds. 2. FRAME_OWNED_BY_CSTACK removed from _frameowner enum. Native changes: - frame.cc: exhaustive switch on _frameowner (no default: so -Wswitch fires on new values); FRAME_OWNED_BY_CSTACK compiled out on 3.15+; three-tier is_entry assignment for 3.15+ / 3.12-3.14 / pre-3.12 - cpython/tasks.h: new PyGen_yf branch for 3.15 with renumbered frame states; FRAME_SUSPENDED_YIELD_FROM_LOCKED guarded by ifdef Py_GIL_DISABLED New C++ tests: - test_cpython_layout_contracts.cpp: static_assert for every CPython enum echion depends on -- fires at build time before any test runner - test_frame_state_315.cpp: gtest suite for PyGen_yf under all 3.15 frame states including the free-threaded FRAME_SUSPENDED_YIELD_FROM_LOCKED state Free-threaded Python 3.15 compiles correctly but is not yet validated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_GatheringFuture and _wait are asyncio internals with no stability contract. Wrap each with hasattr so removal/rename in a future CPython release degrades gracefully (task linking silently skipped) instead of AttributeError at profiler startup. Also adds AIDEV-TODO comments flagging the asyncio policy system deprecation in 3.15 (CPython #127949, removal in 3.16) for the next CPython port.
…asyncio internals On Python <= 3.15 (validated versions), missing _GatheringFuture or _wait is a real bug — raise RuntimeError immediately so it is never silently swallowed. On Python >= 3.16 (where the asyncio policy system is being removed and these internals may disappear), degrade gracefully with a log.warning so the profiler still starts with reduced task-link coverage.
…emoval Apply same tiered hasattr + RuntimeError/log.warning pattern to _scheduled_tasks and _all_tasks in _call_init_asyncio(). On Python <=3.15 (validated versions) a missing attribute raises immediately; on Python >=3.16 it degrades gracefully with a warning. _eager_tasks uses getattr fallback since it was added in 3.12 and absence is not an error.
Python 3.15's pyconfig.h defines _POSIX_C_SOURCE 202405L. If system headers (pulled in via libdatadog_helpers.hpp → features.h) are included first they define the older 200809L value, causing a -Werror redefinition on Linux aarch64 with Python 3.15 headers.
…3.15 builds Transitive deps (e.g. rpds-py) still ship pyo3 0.27.x which caps at Python 3.14. Setting this flag lets pyo3-build-config use the stable ABI instead of failing the version check. Harmless on 3.9-3.14.
The test was using the default tracer=ddtrace.tracer, causing ddup.upload() to flush any pending spans from prior tests. With no agent running, this logged a writer error that polluted caplog and broke the assertion.
Patch ddup.upload so the test only verifies scheduler logging and does not trigger a real upload attempt (which logs a writer connection error and pollutes caplog on Python 3.15 due to test ordering).
Stack profiler fails to collect wall-time samples in uwsgi worker subprocesses on Python 3.15. Mark xfail while this is investigated.
short_task frame capture not verified on Python 3.15 yet; under investigation.
… outside running loop on py3.15
1. Qualify release note with known xfails: uwsgi wall-time sampling and
asyncio short-task frame capture are not yet fully verified on Python 3.15.
2. Fix asyncio import order in verify_profiler_compatibility.py: import
ddtrace.profiling._asyncio first so the ModuleWatchdog callback is
registered before asyncio enters sys.modules, then pop asyncio and
re-import it so the callback is guaranteed to fire during the test.
3. Align run-profiling-tests usage comment with actual PYTHON_VERSION default
("3.15", resolved by pyenv to the installed patch version e.g. 3.15.0a7).
# Conflicts:
# scripts/verify_profiler_compatibility.py
# Conflicts:
# scripts/run-profiling-tests
….14+ PyFrame_GetBack was deprecated in Python 3.11 and removed in 3.14 (PEP 667). It is also absent from pyo3-ffi stable/limited-API bindings activated for Python 3.15 via PYO3_USE_ABI3_FORWARD_COMPATIBILITY. Gate behind not(any(Py_3_14, Py_LIMITED_API)); on 3.14+ advance_frame returns null_mut() stopping traversal after the top frame. Crash reports on 3.14+ show only the top frame until PyUnstable_Frame_GetBack lands in pyo3-ffi bindings.
`(3, 15)` was added to `SUPPORTED_PYTHON_VERSIONS` as part of the 3.15 migration, but the `select_pys` doctests still listed the pre-3.15 output and made `hatch run lint:riot` fail.
d379be4 to
8f689cf
Compare
df58d18 to
3a81fa8
Compare
8f689cf to
4cada08
Compare
|
Closing in favor of #17624, which better isolates profiling-only changes. |
< Prev PR | Next PR >
https://datadoghq.atlassian.net/browse/PROF-14200
Description
Adds Python 3.15 support to the Continuous Profiler. CPython 3.15 made two breaking
internal ABI changes in the frame-reading hot path, requiring updates to both the native
C++ stack profiler (Echion) and the Python-side asyncio integration.
Changes
Native stack profiler (C++,
stack/):frame.ccandcpython/tasks.hfor two CPython 3.15 ABI breaks:PyFrameStateenum fully renumbered,
FRAME_OWNED_BY_CSTACKremoved from_frameownertest_cpython_layout_contracts.cpp—static_asserts for everyCPython enum echion depends on; fires at build time if values drift in a future version
test_frame_state_315.cpp— gtest suite forPyGen_yfunder 3.15frame states
Python asyncio integration (
ddtrace/profiling/_asyncio.py):hasattrguards for private asyncio APIs (_GatheringFuture,_wait,_scheduled_tasks,_all_tasks) that have no stability contractimmediately; on ≥ 3.16 (where the asyncio policy system is being removed) it degrades
gracefully with a warning
AIDEV-TODOcomments marking the asyncio policy system deprecation in 3.15(CPython #127949, removal in 3.16) for the next port
Build and CI:
pyproject.toml,riotfile.py: Python 3.15 added to supported versions and profilingvenv matrix
profiling_nativesanitizer matrix, packagebuild, testrunner, and version generation workflows
docker/.python-version:3.15.0a7Testing
Existing unit tests
All profiling riot test suites PASS
New ABI tests for the stack profiler
Validated locally against Python
3.15.0a7Risks
pyo3/libdatadog unblocks and a stable 3.15 is available
Additional notes