Skip to content

Commit f14a7ba

Browse files
feat(python): add cpu-time gate scenarios for 3.14/3.15 migration
Add paired python_cpu scenarios asserting stack cpu-time samples and thread-name labels on the migration gate path, growing coverage from 10 to 12 scenarios.
1 parent 85b3603 commit f14a7ba

11 files changed

Lines changed: 222 additions & 5 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 [6-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 [8-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_cpu_3.14/requirements.txt /app/requirements.txt
5+
RUN pip install -r /app/requirements.txt
6+
7+
COPY ./scenarios/python_cpu_3.14/main.py /app/main.py
8+
WORKDIR /app
9+
10+
ENV EXECUTION_TIME_SEC=10
11+
ENV _DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0
12+
ENV DD_PROFILING_ENABLED=true
13+
ENV DD_PROFILING_MEMORY_ENABLED=false
14+
15+
CMD ddtrace-run python main.py
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
## CPU stack profiling (3.14 baseline)
2+
3+
Validates that the Datadog Python profiler's **stack collector** reports
4+
`cpu-time` samples with correct call stacks and `thread name` labels under a
5+
CPU-bound workload. This is the **3.14 baseline** half of the `3.14 -> 3.15`
6+
migration pair (see `python_cpu_3.15`).
7+
8+
Reuses the workload from `scenarios/python_cpu`: two tight loops (`a` and `b`)
9+
with a 2:1 relative CPU share. Memory profiling is disabled so the assertion
10+
targets stack samples only.
11+
12+
## Expected profile
13+
14+
- `cpu-time`:
15+
- `<module>;.*main;.*b` ~= 66% (`MainThread`)
16+
- `<module>;.*main;.*a` ~= 33% (`MainThread`)
17+
18+
`cpu-time` is best-effort per sample, so `error_margin` is generous.
19+
`scale_by_duration` normalizes across run lengths.
20+
21+
## Notes
22+
23+
The 3.14 scenario runs on prof-correctness `main` CI (PyPI ddtrace). The 3.15
24+
candidate runs only via the dd-trace-py downstream gate (`DDTRACE_INSTALL_URL`).
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"test_name": "python_cpu_3.14",
3+
"stacks": [
4+
{
5+
"profile-type": "cpu-time",
6+
"stack-content": [
7+
{
8+
"regular_expression": "<module>;.*main;.*b",
9+
"percent": 66,
10+
"error_margin": 100,
11+
"labels": [
12+
{
13+
"key": "thread name",
14+
"values": [
15+
"MainThread"
16+
]
17+
}
18+
]
19+
},
20+
{
21+
"regular_expression": "<module>;.*main;.*a",
22+
"percent": 33,
23+
"error_margin": 100,
24+
"labels": [
25+
{
26+
"key": "thread name",
27+
"values": [
28+
"MainThread"
29+
]
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"scale_by_duration": true
37+
}

scenarios/python_cpu_3.14/main.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
from time import time
3+
4+
5+
class CPUBurner:
6+
def __init__(self) -> None:
7+
self.x = 0
8+
self.i = 0
9+
10+
def a(self) -> None:
11+
self.i = 0
12+
while self.i < 1000000:
13+
self.x += self.i
14+
self.i += 1
15+
16+
def b(self) -> None:
17+
self.i = 0
18+
while self.i < 2000000:
19+
self.x += self.i
20+
self.i += 1
21+
22+
def main(self) -> None:
23+
execution_time_sec = float(os.getenv("EXECUTION_TIME_SEC", "10"))
24+
end = time() + execution_time_sec
25+
while time() < end:
26+
self.a()
27+
self.b()
28+
29+
30+
CPUBurner().main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ddtrace
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ARG BASE_IMAGE="prof-python-3.15"
2+
FROM $BASE_IMAGE
3+
4+
# ddtrace is pre-installed in the base image when DDTRACE_INSTALL_URL is set
5+
# (dd-trace-py downstream CI). Do not pip-install here — PyPI wheels may not
6+
# exist for 3.15 yet.
7+
8+
COPY ./scenarios/python_cpu_3.15/main.py /app/main.py
9+
WORKDIR /app
10+
11+
ENV EXECUTION_TIME_SEC=10
12+
ENV _DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0
13+
ENV DD_PROFILING_ENABLED=true
14+
ENV DD_PROFILING_MEMORY_ENABLED=false
15+
16+
CMD ddtrace-run python main.py
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## CPU stack profiling (3.15 candidate)
2+
3+
Validates that the Datadog Python profiler's **stack collector** reports
4+
`cpu-time` samples with correct call stacks and `thread name` labels under a
5+
CPU-bound workload. This is the **3.15 candidate** half of the `3.14 -> 3.15`
6+
migration pair (see `python_cpu_3.14`).
7+
8+
Reuses the workload from `scenarios/python_cpu`: two tight loops (`a` and `b`)
9+
with a 2:1 relative CPU share. Memory profiling is disabled so the assertion
10+
targets stack samples only.
11+
12+
## Expected profile
13+
14+
- `cpu-time`:
15+
- `<module>;.*main;.*b` ~= 66% (`MainThread`)
16+
- `<module>;.*main;.*a` ~= 33% (`MainThread`)
17+
18+
`cpu-time` is best-effort per sample, so `error_margin` is generous.
19+
`scale_by_duration` normalizes across run lengths.
20+
21+
## Notes
22+
23+
This scenario is wheel-only: PyPI ddtrace wheels may not exist for 3.15 yet, so
24+
it is excluded from prof-correctness `main` CI and runs via the dd-trace-py
25+
downstream gate (`DDTRACE_INSTALL_URL`).
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"test_name": "python_cpu_3.15",
3+
"stacks": [
4+
{
5+
"profile-type": "cpu-time",
6+
"stack-content": [
7+
{
8+
"regular_expression": "<module>;.*main;.*b",
9+
"percent": 66,
10+
"error_margin": 100,
11+
"labels": [
12+
{
13+
"key": "thread name",
14+
"values": [
15+
"MainThread"
16+
]
17+
}
18+
]
19+
},
20+
{
21+
"regular_expression": "<module>;.*main;.*a",
22+
"percent": 33,
23+
"error_margin": 100,
24+
"labels": [
25+
{
26+
"key": "thread name",
27+
"values": [
28+
"MainThread"
29+
]
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"scale_by_duration": true
37+
}

scenarios/python_cpu_3.15/main.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
from time import time
3+
4+
5+
class CPUBurner:
6+
def __init__(self) -> None:
7+
self.x = 0
8+
self.i = 0
9+
10+
def a(self) -> None:
11+
self.i = 0
12+
while self.i < 1000000:
13+
self.x += self.i
14+
self.i += 1
15+
16+
def b(self) -> None:
17+
self.i = 0
18+
while self.i < 2000000:
19+
self.x += self.i
20+
self.i += 1
21+
22+
def main(self) -> None:
23+
execution_time_sec = float(os.getenv("EXECUTION_TIME_SEC", "10"))
24+
end = time() + execution_time_sec
25+
while time() < end:
26+
self.a()
27+
self.b()
28+
29+
30+
CPUBurner().main()

0 commit comments

Comments
 (0)