Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/generate-package-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ jobs:
with:
python-version: "3.14"

- name: Setup Python 3.15
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.15"

- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0

Expand Down
16 changes: 16 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,22 @@ profiling_native:
- PYTHON_VERSION: ["3.12", "3.14"]
SANITIZER: ["valgrind"]

# AIDEV-TODO(py315): fold 3.15 back into the profiling_native matrix once the
# dd/images/dd-trace-py/profiling_native base image installs a Python 3.15
# pyenv version. Today it only carries 3.9–3.14, so PYENV_VERSION=3.15 fails
# with "pyenv: version `3.15' is not installed". allow_failure keeps the
# pipeline green while the upstream image catches up; delete this job and
# re-add "3.15" to the PYTHON_VERSION arrays above once the image is updated.
profiling_native_py315:
extends: .profiling_native_base
allow_failure: true
retry: 2
rules: !reference [profiling_native, rules]
parallel:
matrix:
- PYTHON_VERSION: ["3.15"]
SANITIZER: ["safety", "thread", "", "valgrind"]

test-dd-sts:
stage: tests
needs: []
Expand Down
2 changes: 2 additions & 0 deletions .gitlab/templates/build-base-venvs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ build_base_venvs:
- core.*
- ddtrace/**/*.so*
- .riot/venv_*
- ddtrace/internal/datadog/profiling/test/test_*
- ddtrace/internal/datadog/profiling/test/py315/test_*
34 changes: 34 additions & 0 deletions .riot/requirements/1c6cb02.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# This file is autogenerated by pip-compile with Python 3.15
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c6cb02.in
#
attrs==26.1.0
coverage[toml]==7.13.5
gunicorn==25.3.0
hypothesis==6.45.0
iniconfig==2.3.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
mock==5.2.0
numpy==2.4.4
opentracing==2.4.0
packaging==26.1
pluggy==1.6.0
protobuf==7.34.1
py-cpuinfo==8.0.0
pygments==2.20.0
pytest==9.0.3
pytest-asyncio==0.21.1
pytest-benchmark==5.2.3
pytest-cov==7.1.0
pytest-cpp==2.6.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
referencing==0.37.0
rpds-py==0.30.0
sortedcontainers==2.4.0
uvloop==0.22.1
uwsgi==2.0.31
zstandard==0.25.0
45 changes: 45 additions & 0 deletions .riot/requirements/222bcd0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# This file is autogenerated by pip-compile with Python 3.15
# by the following command:
#
<<<<<<<< HEAD:.riot/requirements/222bcd0.txt
# pip-compile --allow-unsafe --no-annotate .riot/requirements/222bcd0.in
#
attrs==26.1.0
cloudpickle==3.1.2
coverage[toml]==7.14.3
execnet==2.1.2
gevent==26.5.0
greenlet==3.5.3
httpretty==1.1.4
========
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1857594.in
#
attrs==26.1.0
coverage[toml]==7.13.5
>>>>>>>> 49c1ffeaab (ci(profiling): wire py3.15 into build matrix, riotfile, and CI):.riot/requirements/1857594.txt
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.1
pluggy==1.6.0
pyfakefs==6.2.0
pygments==2.20.0
pytest==8.4.2
pytest-asyncio==0.23.8
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
python-json-logger==2.0.7
sortedcontainers==2.4.0
<<<<<<<< HEAD:.riot/requirements/222bcd0.txt
uwsgi==2.0.31
wrapt==2.2.2
========
>>>>>>>> 49c1ffeaab (ci(profiling): wire py3.15 into build matrix, riotfile, and CI):.riot/requirements/1857594.txt
zope-event==5.0
zope-interface==7.2

# The following packages are considered to be unsafe in a requirements file:
setuptools==81.0.0
33 changes: 33 additions & 0 deletions .riot/requirements/95077af.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# This file is autogenerated by pip-compile with Python 3.15
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/95077af.in
#
attrs==26.1.0
coverage[toml]==7.13.5
gunicorn==25.3.0
hypothesis==6.45.0
iniconfig==2.3.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
mock==5.2.0
numpy==2.4.4
opentracing==2.4.0
packaging==26.1
pluggy==1.6.0
protobuf==7.34.1
py-cpuinfo==8.0.0
pygments==2.20.0
pytest==9.0.3
pytest-asyncio==0.21.1
pytest-benchmark==5.2.3
pytest-cov==7.1.0
pytest-cpp==2.6.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
referencing==0.37.0
rpds-py==0.30.0
sortedcontainers==2.4.0
uwsgi==2.0.31
zstandard==0.25.0
37 changes: 37 additions & 0 deletions .riot/requirements/e26245b.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#
# This file is autogenerated by pip-compile with Python 3.15
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/e26245b.in
#
attrs==26.1.0
coverage[toml]==7.13.5
gevent==26.4.0
greenlet==3.4.0
gunicorn[gevent]==25.3.0
hypothesis==6.45.0
iniconfig==2.3.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
mock==5.2.0
numpy==2.4.4
opentracing==2.4.0
packaging==26.1
pluggy==1.6.0
protobuf==7.34.1
py-cpuinfo==8.0.0
pygments==2.20.0
pytest==9.0.3
pytest-asyncio==0.21.1
pytest-benchmark==5.2.3
pytest-cov==7.1.0
pytest-cpp==2.6.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
referencing==0.37.0
rpds-py==0.30.0
sortedcontainers==2.4.0
uwsgi==2.0.31
zope-event==6.1
zope-interface==8.3
zstandard==0.25.0
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ endif()
message(WARNING "SOURCE_LIB_DIR: ${SOURCE_LIB_DIR}")
message(WARNING "LIBRARY_NAME: ${LIBRARY_NAME}")

# We expect the native extension to be built and installed the headers in the following directory. It is configured in
# setup.py by setting CARGO_TARGET_DIR environment variable.
set(SOURCE_INCLUDE_DIR
${CMAKE_SOURCE_DIR}/../../../../../src/native/target${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/include)
# Resolves NATIVE_HEADERS_DIR (libdatadog's generated C headers for the active Python minor). See NativeHeaders.cmake
# for the resolution policy.
include(NativeHeaders)
set(SOURCE_INCLUDE_DIR "${NATIVE_HEADERS_DIR}")

set(DEST_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(DEST_INCLUDE_DIR ${DEST_LIB_DIR}/include)
Expand Down
28 changes: 28 additions & 0 deletions ddtrace/internal/datadog/profiling/cmake/NativeHeaders.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Resolves NATIVE_HEADERS_DIR — the absolute path to libdatadog's generated C headers (produced by the Rust crate under
# src/native/ and written to target<MAJOR>.<MINOR>/include).
#
# Primary source: setup.py passes -DRUST_GENERATED_HEADERS_DIR=<abs path> to every CMake invocation via
# _get_common_cmake_args. Whenever that variable is set, we trust it.
#
# Fallback: build_standalone.sh does NOT pass RUST_GENERATED_HEADERS_DIR, so we compute a path relative to this module's
# own location. Callers must have already invoked find_package(Python3) so that Python3_VERSION_MAJOR/_MINOR are
# defined; the fallback uses those to pick the right per-minor target directory (matching setup.py's CARGO_TARGET_DIR
# layout).
#
# Consumers must have "${CMAKE_CURRENT_SOURCE_DIR}/../cmake" on CMAKE_MODULE_PATH before calling include(NativeHeaders).

if(DEFINED RUST_GENERATED_HEADERS_DIR)
set(NATIVE_HEADERS_DIR "${RUST_GENERATED_HEADERS_DIR}")
else()
if(NOT DEFINED Python3_VERSION_MAJOR OR NOT DEFINED Python3_VERSION_MINOR)
message(
FATAL_ERROR
"NativeHeaders: RUST_GENERATED_HEADERS_DIR is not set and Python3_VERSION_MAJOR/_MINOR are undefined. "
"Call find_package(Python3) before include(NativeHeaders), or pass -DRUST_GENERATED_HEADERS_DIR "
"(as setup.py does).")
endif()
get_filename_component(
NATIVE_HEADERS_DIR
"${CMAKE_CURRENT_LIST_DIR}/../../../../../src/native/target${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/include"
ABSOLUTE)
endif()
10 changes: 7 additions & 3 deletions ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#include "sample.hpp"

// TODO(py-315): Python.h must be included first, before any system or project headers.
// CPython's pyconfig.h defines _POSIX_C_SOURCE and _XOPEN_SOURCE to their current
// POSIX standard values (202405L on 3.15+). If system headers (included transitively
// via libdatadog_helpers.hpp → features.h) are pulled in first, they define older
// values (200809L), and pyconfig.h's later redefinition triggers -Werror on GCC/Clang.
#define PY_SSIZE_T_CLEAN

#include <Python.h>
#include <frameobject.h>

#include "sample.hpp"

#include "libdatadog_helpers.hpp"
#include "profiler_state.hpp"
#include "pymacro.hpp"
Expand Down
13 changes: 7 additions & 6 deletions ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ add_library(${EXTENSION_NAME} SHARED ${DDUP_CPP_SRC})
add_ddup_config(${EXTENSION_NAME})

# Cython generates code that produces errors for the following, so relax compile options
target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address)
# -Wno-missing-field-initializers: Python 3.15 added tp_iteritem to PyTypeObject; Cython doesn't initialize it yet
target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address
-Wno-missing-field-initializers)

# cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior,
# which is required to ensure all paths can be inferred correctly by setup.py.
Expand All @@ -87,11 +89,10 @@ elseif(UNIX)
endif()
endif()

target_include_directories(
${EXTENSION_NAME}
PRIVATE ../dd_wrapper/include
../../../../../src/native/target${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/include/
${Datadog_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS})
include(NativeHeaders)

target_include_directories(${EXTENSION_NAME} PRIVATE ../dd_wrapper/include "${NATIVE_HEADERS_DIR}"
${Datadog_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS})

target_link_libraries(${EXTENSION_NAME} PRIVATE dd_wrapper)

Expand Down
10 changes: 6 additions & 4 deletions ddtrace/internal/datadog/profiling/stack/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,16 @@ add_clangtidy_target(${EXTENSION_NAME})
# Never build with native unwinding, since this is not currently used
target_compile_definitions(${EXTENSION_NAME} PRIVATE UNWIND_NATIVE_DISABLE)

# Resolves NATIVE_HEADERS_DIR (libdatadog's generated C headers for the active Python minor). See
# cmake/NativeHeaders.cmake for the resolution policy.
include(NativeHeaders)

# Includes; echion and python are marked "system" to suppress warnings
target_include_directories(
${EXTENSION_NAME} PRIVATE .. # include dd_wrapper from the root in order to make its paths transparent in the code
include)
target_include_directories(
${EXTENSION_NAME} SYSTEM
PRIVATE ${Python3_INCLUDE_DIRS} echion include/vendored include/util
../../../../../src/native/target${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/include/)
target_include_directories(${EXTENSION_NAME} SYSTEM PRIVATE ${Python3_INCLUDE_DIRS} echion include/vendored
include/util "${NATIVE_HEADERS_DIR}")

# Echion sources need to be given the current platform
if(APPLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,57 @@ extern "C"
#define RESUME_QUICK INSTRUMENTED_RESUME
#endif

#if PY_VERSION_HEX >= 0x030e0000
// Python 3.14+: Use stackpointer and _PyStackRef
#if PY_VERSION_HEX >= 0x030f0000
// Python 3.15+: FRAME_SUSPENDED_YIELD_FROM_LOCKED is a new frame state for
// generators that are locked during a yield-from in free-threaded builds.
// In GIL builds this state is unreachable, so we only check it under
// Py_GIL_DISABLED. All other logic is identical to 3.14 (stackpointer/_PyStackRef).

inline PyObject* PyGen_yf(PyGenObject* gen, PyObject* frame_addr)
{
if (gen->gi_frame_state != FRAME_SUSPENDED_YIELD_FROM
#ifdef Py_GIL_DISABLED
&& gen->gi_frame_state != FRAME_SUSPENDED_YIELD_FROM_LOCKED
#endif
) {
return nullptr;
}

_PyInterpreterFrame frame;
if (copy_type(frame_addr, frame)) {
return nullptr;
}

PyCodeObject code;
auto code_addr = reinterpret_cast<PyCodeObject*>(BITS_TO_PTR_MASKED(frame.f_executable));
if (copy_type(code_addr, code)) {
return nullptr;
}

uintptr_t frame_addr_uint = reinterpret_cast<uintptr_t>(frame_addr);
uintptr_t localsplus_addr = frame_addr_uint + offsetof(_PyInterpreterFrame, localsplus);
uintptr_t stackbase_addr = localsplus_addr + code.co_nlocalsplus * sizeof(_PyStackRef);

uintptr_t stackpointer_addr = reinterpret_cast<uintptr_t>(frame.stackpointer);
if (stackpointer_addr <= stackbase_addr) {
return nullptr;
}

int stacktop = static_cast<int>((stackpointer_addr - stackbase_addr) / sizeof(_PyStackRef));
if (stacktop < 1 || stacktop > MAX_STACK_SIZE) {
return nullptr;
}

_PyStackRef top_ref;
if (copy_type(reinterpret_cast<void*>(stackpointer_addr - sizeof(_PyStackRef)), top_ref)) {
return nullptr;
}

return BITS_TO_PTR_MASKED(top_ref);
}

#elif PY_VERSION_HEX >= 0x030e0000
// Python 3.14: Use stackpointer and _PyStackRef

inline PyObject* PyGen_yf(PyGenObject* gen, PyObject* frame_addr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ function(add_fuzz_target TARGET_NAME)

# Include paths: ../.. is the profiling root (for "dd_wrapper/include/..." paths), ../include is for stack headers.
target_include_directories(${TARGET_NAME} PRIVATE ../.. ../include)
target_include_directories(
${TARGET_NAME} SYSTEM
PRIVATE ${Python3_INCLUDE_DIRS} ../echion ../include/vendored ../include/util
../../../../../../src/native/target${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/include/)
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${Python3_INCLUDE_DIRS} ../echion ../include/vendored
../include/util "${NATIVE_HEADERS_DIR}")

# Ensure echion headers take the fuzz hook in vm.h
target_compile_definitions(${TARGET_NAME} PRIVATE ECHION_FUZZING)
Expand Down
Loading
Loading