Skip to content

Commit 7b66c66

Browse files
feat(python): add cross-cutting gate scenarios for 3.14/3.15
Add paired gil_contention, uvloop, and gevent scenarios for integration shapes on the migration gate path. Gate grows from 20 to 26 scenarios.
1 parent 0eef449 commit 7b66c66

23 files changed

Lines changed: 1010 additions & 20 deletions

File tree

README.md

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

7979
- `dd_trace_py_commit_sha` — commit to test (required)
80-
- `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.*`)
80+
- `test_scenarios` — regexp passed to `TEST_SCENARIOS` (dd-trace-py passes the [22-scenario 3.14/3.15 migration gate](scenarios/python_downstream_gate/README.md); the downstream workflow default alone is `python.*`)
8181

8282
## Creating new tests
8383

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Python downstream gate (dd-trace-py)
22

3-
Sixteen prof-correctness scenarios exercise the profiling stack on **3.14
3+
Twenty-two prof-correctness scenarios exercise the profiling stack on **3.14
44
(baseline)** and **3.15 (candidate)** for the same workloads. They are the
55
default set when dd-trace-py triggers downstream CI on profiling changes.
66

@@ -13,39 +13,29 @@ default set when dd-trace-py triggers downstream CI on profiling changes.
1313
| asyncio (task labels) | `python_asyncio_3.14` | `python_asyncio_3.15` |
1414
| native-cpu | `python_native_cpu_3.14` | `python_native_cpu_3.15` |
1515
| deep-stack | `python_deep_stack_3.14` | `python_deep_stack_3.15` |
16+
| gil-contention | `python_gil_contention_3.14` | `python_gil_contention_3.15` |
17+
| uvloop | `python_uvloop_3.14` | `python_uvloop_3.15` |
18+
| gevent | `python_gevent_3.14` | `python_gevent_3.15` |
1619
| exceptions | `python_exceptions_3.14` | `python_exceptions_3.15` |
1720
| async-gen | `python_async_gen_3.14` | `python_async_gen_3.15` |
1821
| lock | `python_lock_3.14` | `python_lock_3.15` |
1922

20-
## Profiler coverage
21-
22-
| Family | Profile type asserted |
23-
|--------|----------------------|
24-
| cpu (stack) | `cpu-time` + `thread name` |
25-
| alloc (memory) | `alloc-space` + `alloc-samples` |
26-
| asyncio | `wall-time` + `task name` |
27-
| native-cpu | `cpu-time` + `wall-time` (C-extension frames) |
28-
| deep-stack | `cpu-time` (400-frame unwinding) |
29-
| exceptions | `exception-samples` + `exception type` |
30-
| async-gen | `wall-time` |
31-
| lock | `lock-acquire` + `lock-release` + `lock name` |
32-
33-
Feature-specific pairs (mem_domain, live_heap) and cross-cutting integration
34-
scenarios land in follow-up PRs.
35-
3623
## Default downstream regexp
3724

3825
```
39-
python_(cpu|alloc|asyncio|native_cpu|deep_stack|exceptions|async_gen|lock)_3\.(14|15)
26+
python_(cpu|alloc|asyncio|native_cpu|deep_stack|gil_contention|uvloop|gevent|exceptions|async_gen|lock)_3\.(14|15)
4027
```
4128

4229
## Local run
4330

4431
```sh
4532
export DDTRACE_INSTALL_URL="https://dd-trace-py-builds.s3.amazonaws.com/<commit-sha>/install.sh"
46-
TEST_SCENARIOS='python_(cpu|alloc|asyncio|native_cpu|deep_stack|exceptions|async_gen|lock)_3\.(14|15)' go test -v -run TestScenarios
33+
TEST_SCENARIOS='python_(cpu|alloc|asyncio|native_cpu|deep_stack|gil_contention|uvloop|gevent|exceptions|async_gen|lock)_3\.(14|15)' go test -v -run TestScenarios
4734
```
4835

36+
Feature-specific pairs (`mem_domain`, `live_heap`) are separate PRs stacked on
37+
`vlad/gate-infra`.
38+
4939
## Further reading
5040

5141
- prof-correctness downstream wiring: [README](../../README.md#downstream-from-dd-trace-py)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ARG BASE_IMAGE="prof-python-3.14"
2+
FROM $BASE_IMAGE
3+
4+
COPY ./scenarios/python_gevent_3.14/main.py \
5+
./scenarios/python_gevent_3.14/requirements.txt \
6+
/app/
7+
8+
WORKDIR /app
9+
RUN pip install -r requirements.txt
10+
11+
ENV EXECUTION_TIME_SEC=2
12+
ENV DD_PROFILING_ENABLED=true
13+
14+
CMD ddtrace-run python main.py
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
{
2+
"test_name": "python_gevent_3.14",
3+
"scale_by_duration": false,
4+
"pprof-regex": "",
5+
"stacks": [
6+
{
7+
"profile-type": "wall-time",
8+
"pprof-regex": "",
9+
"stack-content": [
10+
{
11+
"regular_expression": "^run$",
12+
"value": 2000000000,
13+
"error_margin": 25,
14+
"labels": [
15+
{
16+
"key": "task name",
17+
"values": [
18+
"Hub"
19+
],
20+
"values_regex": ""
21+
}
22+
]
23+
}
24+
]
25+
},
26+
{
27+
"profile-type": "wall-time",
28+
"pprof-regex": "",
29+
"stack-content": [
30+
{
31+
"regular_expression": ".*target.*",
32+
"value": 1000000000,
33+
"error_margin": 25,
34+
"labels": [
35+
{
36+
"key": "task name",
37+
"values": [
38+
"Greenlet-0"
39+
],
40+
"values_regex": ""
41+
}
42+
]
43+
}
44+
]
45+
},
46+
{
47+
"profile-type": "wall-time",
48+
"pprof-regex": "",
49+
"stack-content": [
50+
{
51+
"regular_expression": ".*target.*",
52+
"value": 1000000000,
53+
"error_margin": 25,
54+
"labels": [
55+
{
56+
"key": "task name",
57+
"values": [
58+
"Greenlet-1"
59+
],
60+
"values_regex": ""
61+
}
62+
]
63+
}
64+
]
65+
},
66+
{
67+
"profile-type": "wall-time",
68+
"pprof-regex": "",
69+
"stack-content": [
70+
{
71+
"regular_expression": ".*target.*",
72+
"value": 1000000000,
73+
"error_margin": 25,
74+
"labels": [
75+
{
76+
"key": "task name",
77+
"values": [
78+
"Greenlet-2"
79+
],
80+
"values_regex": ""
81+
}
82+
]
83+
}
84+
]
85+
},
86+
{
87+
"profile-type": "wall-time",
88+
"pprof-regex": "",
89+
"stack-content": [
90+
{
91+
"regular_expression": ".*target.*",
92+
"value": 1000000000,
93+
"error_margin": 25,
94+
"labels": [
95+
{
96+
"key": "task name",
97+
"values": [
98+
"Greenlet-3"
99+
],
100+
"values_regex": ""
101+
}
102+
]
103+
}
104+
]
105+
},
106+
{
107+
"profile-type": "wall-time",
108+
"pprof-regex": "",
109+
"stack-content": [
110+
{
111+
"regular_expression": ".*target.*",
112+
"value": 1000000000,
113+
"error_margin": 25,
114+
"labels": [
115+
{
116+
"key": "task name",
117+
"values": [
118+
"Greenlet-4"
119+
],
120+
"values_regex": ""
121+
}
122+
]
123+
}
124+
]
125+
},
126+
{
127+
"profile-type": "wall-time",
128+
"pprof-regex": "",
129+
"stack-content": [
130+
{
131+
"regular_expression": ".*target.*",
132+
"value": 1000000000,
133+
"error_margin": 25,
134+
"labels": [
135+
{
136+
"key": "task name",
137+
"values": [
138+
"Greenlet-5"
139+
],
140+
"values_regex": ""
141+
}
142+
]
143+
}
144+
]
145+
},
146+
{
147+
"profile-type": "wall-time",
148+
"pprof-regex": "",
149+
"stack-content": [
150+
{
151+
"regular_expression": ".*target.*",
152+
"value": 1000000000,
153+
"error_margin": 25,
154+
"labels": [
155+
{
156+
"key": "task name",
157+
"values": [
158+
"Greenlet-6"
159+
],
160+
"values_regex": ""
161+
}
162+
]
163+
}
164+
]
165+
},
166+
{
167+
"profile-type": "wall-time",
168+
"pprof-regex": "",
169+
"stack-content": [
170+
{
171+
"regular_expression": ".*target.*",
172+
"value": 1000000000,
173+
"error_margin": 25,
174+
"labels": [
175+
{
176+
"key": "task name",
177+
"values": [
178+
"Greenlet-7"
179+
],
180+
"values_regex": ""
181+
}
182+
]
183+
}
184+
]
185+
},
186+
{
187+
"profile-type": "wall-time",
188+
"pprof-regex": "",
189+
"stack-content": [
190+
{
191+
"regular_expression": ".*target.*",
192+
"value": 1000000000,
193+
"error_margin": 25,
194+
"labels": [
195+
{
196+
"key": "task name",
197+
"values": [
198+
"Greenlet-8"
199+
],
200+
"values_regex": ""
201+
}
202+
]
203+
}
204+
]
205+
},
206+
{
207+
"profile-type": "wall-time",
208+
"pprof-regex": "",
209+
"stack-content": [
210+
{
211+
"regular_expression": ".*target.*",
212+
"value": 1000000000,
213+
"error_margin": 25,
214+
"labels": [
215+
{
216+
"key": "task name",
217+
"values": [
218+
"Greenlet-9"
219+
],
220+
"values_regex": ""
221+
}
222+
]
223+
}
224+
]
225+
}
226+
]
227+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from gevent import monkey
2+
3+
monkey.patch_all()
4+
5+
import os # noqa: E402
6+
import time # noqa: E402
7+
from threading import Thread # noqa: E402
8+
9+
10+
def target(n: int) -> None:
11+
# Do actual work instead of just sleeping so profiler can capture it
12+
end_time = time.monotonic() + n
13+
count = 0
14+
while time.monotonic() < end_time:
15+
count += 1
16+
if count % 1000 == 0:
17+
# Yield to gevent
18+
time.sleep(0.01)
19+
20+
21+
if __name__ == "__main__":
22+
EXECUTION_TIME_SEC = int(os.environ.get("EXECUTION_TIME_SEC", "2"))
23+
24+
threads = [Thread(target=target, args=(EXECUTION_TIME_SEC / 2,)) for _ in range(10)]
25+
for thread in threads:
26+
thread.start()
27+
28+
target(EXECUTION_TIME_SEC)
29+
30+
for thread in threads:
31+
thread.join()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ddtrace
2+
gevent
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ARG BASE_IMAGE="prof-python-3.15"
2+
FROM $BASE_IMAGE
3+
4+
COPY ./scenarios/python_gevent_3.15/main.py /app/main.py
5+
WORKDIR /app
6+
7+
ENV EXECUTION_TIME_SEC=2
8+
ENV DD_PROFILING_ENABLED=true
9+
10+
CMD ddtrace-run python main.py

0 commit comments

Comments
 (0)