Skip to content

Commit 7c1ccac

Browse files
feat(python): add stack-advanced gate scenarios for 3.14/3.15
Add paired asyncio (task name labels), native_cpu (C-extension frames), and deep_stack (deep unwinding) scenarios. Gate grows from 14 to 20.
1 parent 551bc6b commit 7c1ccac

29 files changed

Lines changed: 637 additions & 22 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Both use [dd-octo-sts](https://github.com/DataDog/dd-octo-sts-action) (`dd-trace
5555
**Inputs:**
5656

5757
- `dd_trace_py_commit_sha` — commit to test (required)
58-
- `test_scenarios` — regexp passed to `TEST_SCENARIOS` (dd-trace-py passes the [10-scenario 3.14/3.15 migration gate](scenarios/python_downstream_gate/README.md); the downstream workflow default alone is `python.*`)
58+
- `test_scenarios` — regexp passed to `TEST_SCENARIOS` (dd-trace-py passes the [16-scenario 3.14/3.15 migration gate](scenarios/python_downstream_gate/README.md); the downstream workflow default alone is `python.*`)
5959

6060
## Creating new tests
6161

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ARG BASE_IMAGE="prof-python-3.14"
2+
FROM $BASE_IMAGE
3+
4+
COPY ./scenarios/python_asyncio_3.14/requirements.txt /app/requirements.txt
5+
RUN pip install -r /app/requirements.txt
6+
7+
COPY ./scenarios/python_asyncio_3.14/main.py /app/main.py
8+
WORKDIR /app
9+
10+
ENV EXECUTION_TIME_SEC=5
11+
ENV ECHION_USE_FAST_COPY_MEMORY=1
12+
ENV _DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0
13+
ENV DD_PROFILING_ENABLED=true
14+
15+
CMD ddtrace-run python main.py
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Asyncio task-name labels (3.14 baseline)
2+
3+
Validates **wall-time** stack samples carry asyncio `task name` labels (named and
4+
unnamed tasks). Migration pair baseline; see `python_asyncio_3.15`.
5+
6+
Reuses `scenarios/python_asyncio_3.11` workload.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"test_name": "python_asyncio_3.14",
3+
"stacks": [
4+
{
5+
"profile-type": "wall-time",
6+
"stack-content": [
7+
{
8+
"regular_expression": ".*my_coroutine.*",
9+
"percent": 50,
10+
"error_margin": 100,
11+
"labels": [
12+
{
13+
"key": "thread name",
14+
"values": [
15+
"MainThread"
16+
]
17+
},
18+
{
19+
"key": "task name",
20+
"values": [
21+
"short_task"
22+
]
23+
}
24+
]
25+
}
26+
]
27+
},
28+
{
29+
"profile-type": "wall-time",
30+
"stack-content": [
31+
{
32+
"regular_expression": ".*my_coroutine.*",
33+
"percent": 50,
34+
"error_margin": 100,
35+
"labels": [
36+
{
37+
"key": "thread name",
38+
"values": [
39+
"MainThread"
40+
]
41+
},
42+
{
43+
"key": "task name",
44+
"values_regex": "Task-[0-9]+"
45+
}
46+
]
47+
}
48+
]
49+
}
50+
],
51+
"scale_by_duration": false
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import asyncio
2+
import os
3+
4+
5+
async def my_coroutine(n: float) -> None:
6+
await asyncio.sleep(n)
7+
8+
9+
async def main() -> None:
10+
# Simple application that creates two Tasks with different durations:
11+
# - "unnamed Task" runs my_coroutine() for 2 second
12+
# - short_task runs my_coroutine() for 1 second
13+
# The profiler should capture both Tasks with their respective durations.
14+
15+
# Give the Profiler some time to start up
16+
await asyncio.sleep(0.5)
17+
18+
execution_time_sec = float(os.environ.get("EXECUTION_TIME_SEC", "5"))
19+
20+
short_task = asyncio.create_task(my_coroutine(execution_time_sec / 2.0), name="short_task")
21+
22+
# asyncio.gather will automatically wrap my_coroutine into a Task
23+
await asyncio.gather(short_task, my_coroutine(execution_time_sec))
24+
25+
26+
if __name__ == "__main__":
27+
asyncio.run(main())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ddtrace
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ARG BASE_IMAGE="prof-python-3.15"
2+
FROM $BASE_IMAGE
3+
4+
COPY ./scenarios/python_asyncio_3.15/main.py /app/main.py
5+
WORKDIR /app
6+
7+
ENV EXECUTION_TIME_SEC=5
8+
ENV ECHION_USE_FAST_COPY_MEMORY=1
9+
ENV _DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0
10+
ENV DD_PROFILING_ENABLED=true
11+
12+
CMD ddtrace-run python main.py
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Asyncio task-name labels (3.15 candidate)
2+
3+
Wheel-only 3.15 candidate half of the asyncio task-name gate pair. See
4+
`python_asyncio_3.14`.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"test_name": "python_asyncio_3.15",
3+
"stacks": [
4+
{
5+
"profile-type": "wall-time",
6+
"stack-content": [
7+
{
8+
"regular_expression": ".*my_coroutine.*",
9+
"percent": 50,
10+
"error_margin": 100,
11+
"labels": [
12+
{
13+
"key": "thread name",
14+
"values": [
15+
"MainThread"
16+
]
17+
},
18+
{
19+
"key": "task name",
20+
"values": [
21+
"short_task"
22+
]
23+
}
24+
]
25+
}
26+
]
27+
},
28+
{
29+
"profile-type": "wall-time",
30+
"stack-content": [
31+
{
32+
"regular_expression": ".*my_coroutine.*",
33+
"percent": 50,
34+
"error_margin": 100,
35+
"labels": [
36+
{
37+
"key": "thread name",
38+
"values": [
39+
"MainThread"
40+
]
41+
},
42+
{
43+
"key": "task name",
44+
"values_regex": "Task-[0-9]+"
45+
}
46+
]
47+
}
48+
]
49+
}
50+
],
51+
"scale_by_duration": false
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import asyncio
2+
import os
3+
4+
5+
async def my_coroutine(n: float) -> None:
6+
await asyncio.sleep(n)
7+
8+
9+
async def main() -> None:
10+
# Simple application that creates two Tasks with different durations:
11+
# - "unnamed Task" runs my_coroutine() for 2 second
12+
# - short_task runs my_coroutine() for 1 second
13+
# The profiler should capture both Tasks with their respective durations.
14+
15+
# Give the Profiler some time to start up
16+
await asyncio.sleep(0.5)
17+
18+
execution_time_sec = float(os.environ.get("EXECUTION_TIME_SEC", "5"))
19+
20+
short_task = asyncio.create_task(my_coroutine(execution_time_sec / 2.0), name="short_task")
21+
22+
# asyncio.gather will automatically wrap my_coroutine into a Task
23+
await asyncio.gather(short_task, my_coroutine(execution_time_sec))
24+
25+
26+
if __name__ == "__main__":
27+
asyncio.run(main())

0 commit comments

Comments
 (0)