@@ -83,6 +83,81 @@ async def main() -> None:
8383 )
8484
8585
86+ @pytest .mark .subprocess (
87+ env = dict (
88+ DD_PROFILING_OUTPUT_PPROF = "/tmp/test_asyncio_to_thread_origin_task" ,
89+ ),
90+ err = None ,
91+ )
92+ # For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test)
93+ def test_asyncio_to_thread_origin_task_labels () -> None :
94+ # asyncio.to_thread is a higher-level wrapper around run_in_executor; origin
95+ # task labels should still identify the awaiting asyncio task.
96+ import asyncio
97+ import os
98+ import time
99+
100+ from ddtrace import patch
101+ from ddtrace .internal .datadog .profiling import stack
102+ from ddtrace .profiling import profiler
103+ from tests .profiling .collector import pprof_utils
104+ from tests .profiling .collector .test_utils import async_run
105+
106+ assert stack .is_available , stack .failure_msg
107+
108+ patch (futures = True )
109+
110+ def slow_sync_function () -> None :
111+ time .sleep (1 )
112+
113+ async def asynchronous_function () -> None :
114+ await asyncio .to_thread (slow_sync_function )
115+
116+ async def main () -> None :
117+ await asynchronous_function ()
118+
119+ p = profiler .Profiler ()
120+ p .start ()
121+
122+ async_run (main ())
123+
124+ p .stop ()
125+
126+ output_filename = os .environ ["DD_PROFILING_OUTPUT_PPROF" ] + "." + str (os .getpid ())
127+ profile = pprof_utils .parse_newest_profile (output_filename )
128+ string_table = profile .string_table
129+
130+ origin_task_samples = pprof_utils .get_samples_with_label_key (profile , "origin task id" )
131+ assert len (origin_task_samples ) > 0 , "expected to_thread samples labeled with the originating asyncio task"
132+
133+ awaiting_task_ids = set ()
134+ for sample in pprof_utils .get_samples_with_label_key (profile , "task id" ):
135+ awaiting_task_ids .add (pprof_utils .get_label_with_key (string_table , sample , "task id" ).num )
136+ assert awaiting_task_ids , "expected at least one asyncio task sample on the event-loop thread"
137+
138+ for sample in origin_task_samples :
139+ origin_task_id = pprof_utils .get_label_with_key (string_table , sample , "origin task id" ).num
140+ assert origin_task_id in awaiting_task_ids , (
141+ f"origin task id { origin_task_id } does not match any awaiting task id { awaiting_task_ids } "
142+ )
143+ origin_task_name = pprof_utils .get_label_with_key (string_table , sample , "origin task name" )
144+ assert origin_task_name is not None and string_table [origin_task_name .str ], "expected origin task name label"
145+
146+ pprof_utils .assert_profile_has_sample (
147+ profile ,
148+ origin_task_samples ,
149+ expected_sample = pprof_utils .StackEvent (
150+ locations = [
151+ pprof_utils .StackLocation (
152+ function_name = "slow_sync_function" ,
153+ filename = "test_asyncio_executor.py" ,
154+ line_no = slow_sync_function .__code__ .co_firstlineno + 1 ,
155+ ),
156+ ],
157+ ),
158+ )
159+
160+
86161@pytest .mark .subprocess (
87162 env = dict (
88163 DD_PROFILING_OUTPUT_PPROF = "/tmp/test_asyncio_executor" ,
0 commit comments