Skip to content

Commit 49569f2

Browse files
committed
[python_cpu_sleep_gevent_3.12] add CPU/sleep attribution scenario (PROF-14213)
New scenario that alternates 10 ms CPU bursts with 50 ms sleeps in gevent mode with 4 concurrent staggered greenlets, asserting on (a) total CPU consumed (667M ns/wall-sec, ±25%) and (b) cpu_burst self-attribution as % of cpu-time profile (80% ±10%). Both bounds together act as a regression check for the gevent over-count bug fixed by dd-trace-py PR #18222 (Finding 3 in the python-cpu-accuracy survey): if the bug recurs, total CPU would inflate to ~1.07 CPU-sec/wall-sec, well outside the ±25% margin. Adaptive sampling is disabled (_DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0). Also adds base_images/Dockerfile.python-3.12.
1 parent 1c0de05 commit 49569f2

5 files changed

Lines changed: 93 additions & 0 deletions

File tree

base_images/Dockerfile.python-3.12

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.12 AS base
2+
3+
# When set, download and run this install script to pre-install ddtrace.
4+
# Used by upstream dd-trace-py CI to test with just-built wheels from S3.
5+
ARG DDTRACE_INSTALL_URL=""
6+
RUN if [ -n "$DDTRACE_INSTALL_URL" ]; then \
7+
curl -fsSL "$DDTRACE_INSTALL_URL" | bash; \
8+
fi
9+
10+
ENV DD_PROFILING_ENABLED=true
11+
ENV DD_TRACE_ENABLED=false
12+
# ENV DD_TRACE_DEBUG=true
13+
ENV DD_PROFILING_OUTPUT_PPROF="/app/data/profiles"
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.12"
2+
FROM $BASE_IMAGE
3+
4+
WORKDIR /usr/src/app
5+
6+
COPY ./scenarios/python_cpu_sleep_gevent_3.12/ /usr/src/app
7+
8+
RUN chmod 644 /usr/src/app/main.py
9+
RUN pip install --no-cache-dir -r requirements.txt
10+
11+
ENV EXECUTION_TIME_SEC=30
12+
ENV DD_PROFILING_MEMORY_ENABLED=false
13+
ENV _DD_PROFILING_STACK_ADAPTIVE_SAMPLING_ENABLED=0
14+
15+
CMD ddtrace-run python main.py
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"test_name": "python_cpu_sleep_gevent_3.12",
3+
"scale_by_duration": true,
4+
"stacks": [
5+
{
6+
"profile-type": "cpu-time",
7+
"stack-content": [
8+
{
9+
"regular_expression": ".*",
10+
"value": 667000000,
11+
"error_margin": 25
12+
},
13+
{
14+
"regular_expression": ".*cpu_burst.*",
15+
"percent": 80,
16+
"error_margin": 10
17+
}
18+
]
19+
}
20+
]
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from gevent import monkey
2+
3+
monkey.patch_all()
4+
5+
import os # noqa: E402
6+
import time # noqa: E402
7+
8+
import gevent # noqa: E402
9+
10+
11+
def cpu_burst(target_seconds: float) -> int:
12+
deadline = time.monotonic() + target_seconds
13+
x = 0x1234
14+
while time.monotonic() < deadline:
15+
x = (x * 48271) % 2147483647
16+
return x
17+
18+
19+
def slot(cpu_seconds: float, off_cpu: float, deadline: float) -> None:
20+
while time.monotonic() < deadline:
21+
time.sleep(off_cpu)
22+
cpu_burst(cpu_seconds)
23+
24+
25+
def main() -> None:
26+
execution_time_sec = float(os.environ.get("EXECUTION_TIME_SEC", "30"))
27+
cpu_seconds = 0.01
28+
off_cpu = 0.05
29+
concurrency = 4
30+
stagger = 0.01
31+
32+
deadline = time.monotonic() + execution_time_sec
33+
greenlets = []
34+
for i in range(concurrency):
35+
if i > 0:
36+
gevent.sleep(stagger)
37+
greenlets.append(gevent.spawn(slot, cpu_seconds, off_cpu, deadline))
38+
gevent.joinall(greenlets)
39+
40+
41+
if __name__ == "__main__":
42+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ddtrace
2+
gevent>=24.0

0 commit comments

Comments
 (0)