Skip to content

Commit 829131a

Browse files
chore(profiling): upgrade echion (#15311)
## Description This upgrades Echion to the latest version. There were [quite a lot of commits](P403n1x87/echion@e9f06f7...bd10fdb) but mostly just tests being added. ### Changes Relevant PRs: * P403n1x87/echion#184 * P403n1x87/echion#188 Other PRs: * P403n1x87/echion#182 * P403n1x87/echion#185 * P403n1x87/echion#186 * P403n1x87/echion#187
1 parent 2f7da21 commit 829131a

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ endif()
5656

5757
# Add echion
5858
set(ECHION_COMMIT
59-
"e9f06f7f2a716d583e1bd204eab33b12dc970983" # https://github.com/P403n1x87/echion/commit/e9f06f7f2a716d583e1bd204eab33b12dc970983
59+
"bd10fdbdf760fcc3b28dbf12b940b61557770088" # https://github.com/P403n1x87/echion/commit/bd10fdbdf760fcc3b28dbf12b940b61557770088
6060
CACHE STRING "Commit hash of echion to use")
6161
FetchContent_Declare(
6262
echion
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
features:
2+
- |
3+
profiling: The stack sampler supports async generators and ``asyncio.wait``.

tests/profiling/collector/pprof_utils.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,16 @@ def assert_sample_has_locations(profile, sample, expected_locations: Optional[Li
310310
sample_loc_strs.append(f"{filename}:{function_name}:{line_no}")
311311

312312
if expected_locations_idx < len(expected_locations):
313-
if (
314-
function_name.endswith(expected_locations[expected_locations_idx].function_name)
315-
and re.fullmatch(expected_locations[expected_locations_idx].filename, filename)
316-
and line_no == expected_locations[expected_locations_idx].line_no
317-
):
313+
function_name_matches = function_name.endswith(expected_locations[expected_locations_idx].function_name)
314+
filename_matches = expected_locations[expected_locations_idx].filename == "" or re.fullmatch(
315+
expected_locations[expected_locations_idx].filename, filename
316+
)
317+
line_no_matches = (
318+
expected_locations[expected_locations_idx].line_no == -1
319+
or line_no == expected_locations[expected_locations_idx].line_no
320+
)
321+
322+
if function_name_matches and filename_matches and line_no_matches:
318323
expected_locations_idx += 1
319324
if expected_locations_idx == len(expected_locations):
320325
checked = True
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import pytest
2+
3+
4+
@pytest.mark.subprocess(
5+
env=dict(
6+
DD_PROFILING_OUTPUT_PPROF="/tmp/test_async_generator",
7+
),
8+
err=None,
9+
)
10+
# For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test)
11+
def test_async_generator() -> None:
12+
import asyncio
13+
import os
14+
import typing
15+
import uuid
16+
17+
from ddtrace import ext
18+
from ddtrace.internal.datadog.profiling import stack_v2
19+
from ddtrace.profiling import profiler
20+
from ddtrace.trace import tracer
21+
from tests.profiling.collector import pprof_utils
22+
23+
assert stack_v2.is_available, stack_v2.failure_msg
24+
25+
async def deep_dependency():
26+
await asyncio.sleep(0.05)
27+
28+
async def async_generator_dep(i: int) -> typing.AsyncGenerator[int, None]:
29+
for j in range(i):
30+
await deep_dependency()
31+
yield j
32+
33+
async def async_generator() -> typing.AsyncGenerator[int, None]:
34+
for i in range(10):
35+
async for j in async_generator_dep(i):
36+
yield j
37+
38+
async def asynchronous_function() -> None:
39+
async for i in async_generator():
40+
pass
41+
42+
resource = str(uuid.uuid4())
43+
span_type = ext.SpanTypes.WEB
44+
45+
p = profiler.Profiler(tracer=tracer)
46+
p.start()
47+
with tracer.trace("test_asyncio", resource=resource, span_type=span_type) as span:
48+
span_id = span.span_id
49+
local_root_span_id = span._local_root.span_id
50+
51+
loop = asyncio.new_event_loop()
52+
asyncio.set_event_loop(loop)
53+
main_task = loop.create_task(asynchronous_function(), name="asynchronous_function")
54+
loop.run_until_complete(main_task)
55+
56+
p.stop()
57+
58+
output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid())
59+
60+
profile = pprof_utils.parse_newest_profile(output_filename)
61+
62+
samples_with_span_id = pprof_utils.get_samples_with_label_key(profile, "span id")
63+
assert len(samples_with_span_id) > 0
64+
65+
# get samples with task_name
66+
samples = pprof_utils.get_samples_with_label_key(profile, "task name")
67+
# The next fails if stack_v2 is not properly configured with asyncio task
68+
# tracking via ddtrace.profiling._asyncio
69+
assert len(samples) > 0
70+
71+
pprof_utils.assert_profile_has_sample(
72+
profile,
73+
samples,
74+
expected_sample=pprof_utils.StackEvent(
75+
thread_name="MainThread",
76+
task_name="asynchronous_function",
77+
span_id=span_id,
78+
local_root_span_id=local_root_span_id,
79+
locations=[
80+
pprof_utils.StackLocation(
81+
function_name="sleep",
82+
filename="", # r"^.*tasks\.py$",
83+
line_no=-1,
84+
),
85+
pprof_utils.StackLocation(
86+
function_name="deep_dependency",
87+
filename="test_async_generator.py",
88+
line_no=deep_dependency.__code__.co_firstlineno + 1,
89+
),
90+
pprof_utils.StackLocation(
91+
function_name="async_generator_dep",
92+
filename="test_async_generator.py",
93+
line_no=async_generator_dep.__code__.co_firstlineno + 2,
94+
),
95+
pprof_utils.StackLocation(
96+
function_name="async_generator",
97+
filename="test_async_generator.py",
98+
line_no=async_generator.__code__.co_firstlineno + 2,
99+
),
100+
pprof_utils.StackLocation(
101+
function_name="asynchronous_function",
102+
filename="test_async_generator.py",
103+
line_no=asynchronous_function.__code__.co_firstlineno + 1,
104+
),
105+
],
106+
),
107+
)

0 commit comments

Comments
 (0)