Skip to content

Commit 7d7d92e

Browse files
chore(profiling): update echion (for memcpy) (#15030)
## Description This PR updates `dd-trace-py` to use the latest version of Echion. **New commits** * [echion/perf: use memcpy instead of process_vm_readv](P403n1x87/echion#162) ### Notes The main change is the new "fast memory copy" feature that significantly reduces the time it takes to copy memory from the Python process by using a SEGV-guarded `memcpy` (no system call involved) instead of `process_vm_readv`. This feature is **experimental** and we intend to test it within Datadog before making it widely available (and eventually, hopefully making it the default/only available option). Currently, `ddtrace` cannot enable/disable the feature itself; the Echion library relies on the `ECHION_USE_FAST_COPY_MEMORY` environment variable on startup to enable/disable it. ## Testing The change has been tested on benchmarks, in the CI and inside the reliability environment. No crashes are happening.
1 parent aeb5df4 commit 7d7d92e

File tree

10 files changed

+242
-3
lines changed

10 files changed

+242
-3
lines changed

.github/workflows/profiling-native.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
matrix:
2222
os: [ubuntu-24.04]
2323
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
24+
memcpy-mode: ["default", "fast"]
2425
sanitizer: ["safety", "thread", "valgrind"]
2526

2627
steps:
@@ -49,4 +50,10 @@ jobs:
4950
# DEV: We currently have tests in dd_wrapper and stack_v2, setting
5051
# stack_v2 here will also run tests in dd_wrapper. Revisit this when
5152
# that changes.
53+
if [ "${{matrix.memcpy-mode}}" == "fast" ]; then
54+
export ECHION_USE_FAST_COPY_MEMORY=1
55+
else
56+
export ECHION_USE_FAST_COPY_MEMORY=0
57+
fi
58+
5259
./ddtrace/internal/datadog/profiling/build_standalone.sh --${{matrix.sanitizer}} RelWithDebInfo stack_v2_test

.riot/requirements/1217de7.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.9
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1217de7.in
6+
#
7+
attrs==25.4.0
8+
coverage[toml]==7.10.7
9+
exceptiongroup==1.3.0
10+
gunicorn==23.0.0
11+
hypothesis==6.45.0
12+
importlib-metadata==8.7.0
13+
iniconfig==2.1.0
14+
jsonschema==4.25.1
15+
jsonschema-specifications==2025.9.1
16+
lz4==4.4.5
17+
mock==5.2.0
18+
opentracing==2.4.0
19+
packaging==25.0
20+
pluggy==1.6.0
21+
py-cpuinfo==8.0.0
22+
pygments==2.19.2
23+
pytest==8.4.2
24+
pytest-asyncio==0.21.1
25+
pytest-benchmark==5.2.1
26+
pytest-cov==7.0.0
27+
pytest-cpp==2.6.0
28+
pytest-mock==3.15.1
29+
pytest-randomly==4.0.1
30+
referencing==0.36.2
31+
rpds-py==0.27.1
32+
sortedcontainers==2.4.0
33+
tomli==2.3.0
34+
typing-extensions==4.15.0
35+
uwsgi==2.0.31
36+
zipp==3.23.0

.riot/requirements/147b755.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.12
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/147b755.in
6+
#
7+
attrs==25.4.0
8+
coverage[toml]==7.11.0
9+
gunicorn==23.0.0
10+
hypothesis==6.45.0
11+
iniconfig==2.3.0
12+
jsonschema==4.25.1
13+
jsonschema-specifications==2025.9.1
14+
lz4==4.4.5
15+
mock==5.2.0
16+
opentracing==2.4.0
17+
packaging==25.0
18+
pluggy==1.6.0
19+
py-cpuinfo==8.0.0
20+
pygments==2.19.2
21+
pytest==8.4.2
22+
pytest-asyncio==0.21.1
23+
pytest-benchmark==5.2.1
24+
pytest-cov==7.0.0
25+
pytest-cpp==2.6.0
26+
pytest-mock==3.15.1
27+
pytest-randomly==4.0.1
28+
referencing==0.37.0
29+
rpds-py==0.28.0
30+
sortedcontainers==2.4.0
31+
typing-extensions==4.15.0
32+
uwsgi==2.0.31

.riot/requirements/1aa6d22.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.13
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1aa6d22.in
6+
#
7+
attrs==25.4.0
8+
coverage[toml]==7.11.0
9+
gunicorn==23.0.0
10+
hypothesis==6.45.0
11+
iniconfig==2.3.0
12+
jsonschema==4.25.1
13+
jsonschema-specifications==2025.9.1
14+
lz4==4.4.5
15+
mock==5.2.0
16+
opentracing==2.4.0
17+
packaging==25.0
18+
pluggy==1.6.0
19+
py-cpuinfo==8.0.0
20+
pygments==2.19.2
21+
pytest==8.4.2
22+
pytest-asyncio==0.21.1
23+
pytest-benchmark==5.2.1
24+
pytest-cov==7.0.0
25+
pytest-cpp==2.6.0
26+
pytest-mock==3.15.1
27+
pytest-randomly==4.0.1
28+
referencing==0.37.0
29+
rpds-py==0.28.0
30+
sortedcontainers==2.4.0
31+
uwsgi==2.0.31

.riot/requirements/1df66ab.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.11
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1df66ab.in
6+
#
7+
attrs==25.4.0
8+
coverage[toml]==7.11.0
9+
gunicorn==23.0.0
10+
hypothesis==6.45.0
11+
iniconfig==2.3.0
12+
jsonschema==4.25.1
13+
jsonschema-specifications==2025.9.1
14+
lz4==4.4.5
15+
mock==5.2.0
16+
opentracing==2.4.0
17+
packaging==25.0
18+
pluggy==1.6.0
19+
py-cpuinfo==8.0.0
20+
pygments==2.19.2
21+
pytest==8.4.2
22+
pytest-asyncio==0.21.1
23+
pytest-benchmark==5.2.1
24+
pytest-cov==7.0.0
25+
pytest-cpp==2.6.0
26+
pytest-mock==3.15.1
27+
pytest-randomly==4.0.1
28+
referencing==0.37.0
29+
rpds-py==0.28.0
30+
sortedcontainers==2.4.0
31+
typing-extensions==4.15.0
32+
uwsgi==2.0.31

.riot/requirements/ea5d060.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.8
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/ea5d060.in
6+
#
7+
attrs==25.3.0
8+
coverage[toml]==7.6.1
9+
exceptiongroup==1.3.0
10+
gunicorn==23.0.0
11+
hypothesis==6.45.0
12+
importlib-metadata==8.5.0
13+
importlib-resources==6.4.5
14+
iniconfig==2.1.0
15+
jsonschema==4.23.0
16+
jsonschema-specifications==2023.12.1
17+
lz4==4.3.3
18+
mock==5.2.0
19+
opentracing==2.4.0
20+
packaging==25.0
21+
pkgutil-resolve-name==1.3.10
22+
pluggy==1.5.0
23+
py-cpuinfo==8.0.0
24+
pytest==8.3.5
25+
pytest-asyncio==0.21.1
26+
pytest-benchmark==4.0.0
27+
pytest-cov==5.0.0
28+
pytest-cpp==2.6.0
29+
pytest-mock==3.14.1
30+
pytest-randomly==3.15.0
31+
referencing==0.35.1
32+
rpds-py==0.20.1
33+
sortedcontainers==2.4.0
34+
tomli==2.3.0
35+
typing-extensions==4.13.2
36+
uwsgi==2.0.31
37+
zipp==3.20.2

.riot/requirements/fa272ae.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.10
3+
# by the following command:
4+
#
5+
# pip-compile --allow-unsafe --no-annotate .riot/requirements/fa272ae.in
6+
#
7+
attrs==25.4.0
8+
coverage[toml]==7.11.0
9+
exceptiongroup==1.3.0
10+
gunicorn==23.0.0
11+
hypothesis==6.45.0
12+
iniconfig==2.3.0
13+
jsonschema==4.25.1
14+
jsonschema-specifications==2025.9.1
15+
lz4==4.4.5
16+
mock==5.2.0
17+
opentracing==2.4.0
18+
packaging==25.0
19+
pluggy==1.6.0
20+
py-cpuinfo==8.0.0
21+
pygments==2.19.2
22+
pytest==8.4.2
23+
pytest-asyncio==0.21.1
24+
pytest-benchmark==5.2.1
25+
pytest-cov==7.0.0
26+
pytest-cpp==2.6.0
27+
pytest-mock==3.15.1
28+
pytest-randomly==4.0.1
29+
referencing==0.37.0
30+
rpds-py==0.28.0
31+
sortedcontainers==2.4.0
32+
tomli==2.3.0
33+
typing-extensions==4.15.0
34+
uwsgi==2.0.31

ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ endif()
4949

5050
# Add echion
5151
set(ECHION_COMMIT
52-
"1b02a8fcccaa4b748b494f3c3177b78be6503967" # https://github.com/P403n1x87/echion/commit/1b02a8fcccaa4b748b494f3c3177b78be6503967
52+
"43432c5c0a89617b06533215a15d0d6ffbbfd02b" # https://github.com/P403n1x87/echion/commit/43432c5c0a89617b06533215a15d0d6ffbbfd02b
5353
CACHE STRING "Commit hash of echion to use")
5454
FetchContent_Declare(
5555
echion
@@ -62,8 +62,15 @@ if(NOT echion_POPULATED)
6262
endif()
6363

6464
# Specify the target C-extension that we want to build
65-
add_library(${EXTENSION_NAME} SHARED ${echion_SOURCE_DIR}/echion/frame.cc ${echion_SOURCE_DIR}/echion/render.cc
66-
src/sampler.cpp src/stack_renderer.cpp src/stack_v2.cpp src/thread_span_links.cpp)
65+
add_library(
66+
${EXTENSION_NAME} SHARED
67+
${echion_SOURCE_DIR}/echion/frame.cc
68+
${echion_SOURCE_DIR}/echion/render.cc
69+
${echion_SOURCE_DIR}/echion/danger.cc
70+
src/sampler.cpp
71+
src/stack_renderer.cpp
72+
src/stack_v2.cpp
73+
src/thread_span_links.cpp)
6774

6875
# Add common config
6976
add_ddup_config(${EXTENSION_NAME})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
upgrade:
2+
- |
3+
profiling: this updates echion (the Python stack sampler) to the latest version, which introduces an experimental
4+
faster memory copy function.
5+

riotfile.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3474,6 +3474,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
34743474
"gevent": latest,
34753475
},
34763476
),
3477+
# memcpy-based sampler
3478+
Venv(
3479+
env={
3480+
"ECHION_USE_FAST_COPY_MEMORY": "1",
3481+
},
3482+
),
34773483
],
34783484
),
34793485
# Python 3.10
@@ -3506,6 +3512,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
35063512
),
35073513
],
35083514
),
3515+
# memcpy-based sampler
3516+
Venv(
3517+
env={
3518+
"ECHION_USE_FAST_COPY_MEMORY": "1",
3519+
},
3520+
),
35093521
],
35103522
),
35113523
# Python >= 3.11
@@ -3525,6 +3537,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
35253537
},
35263538
pkgs={"gunicorn[gevent]": latest, "gevent": latest},
35273539
),
3540+
# memcpy-based sampler
3541+
Venv(
3542+
env={
3543+
"ECHION_USE_FAST_COPY_MEMORY": "1",
3544+
},
3545+
),
35283546
],
35293547
),
35303548
Venv(

0 commit comments

Comments
 (0)