Skip to content

Commit 563ecff

Browse files
Bruuuh, it worked
1 parent 890474d commit 563ecff

File tree

3 files changed

+37
-111
lines changed

3 files changed

+37
-111
lines changed

Lib/profiling/sampling/collector.py

Lines changed: 35 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -36,119 +36,45 @@ def _iter_all_frames(self, stack_frames, skip_idle=False):
3636
yield frames, thread_info.thread_id
3737

3838
def _iter_async_frames(self, awaited_info_list):
39-
"""
40-
Iterate over all async frame stacks from awaited info.
41-
"""
42-
# First, index all tasks by their IDs so we can look up parents easily
4339
all_tasks = {}
44-
tasks_by_name = {}
40+
4541
for awaited_info in awaited_info_list:
42+
thread_id = awaited_info.thread_id
4643
for task_info in awaited_info.awaited_by:
47-
all_tasks[task_info.task_id] = (task_info, awaited_info.thread_id)
48-
display_name = task_info.task_name or f"Task-{task_info.task_id}"
49-
tasks_by_name.setdefault(display_name, []).append(
50-
(task_info, awaited_info.thread_id)
51-
)
52-
fallback_name = f"Task-{task_info.task_id}"
53-
if fallback_name != display_name:
54-
tasks_by_name.setdefault(fallback_name, []).append(
55-
(task_info, awaited_info.thread_id)
56-
)
57-
58-
# Use a cache for memoizing parent chains so we don't recompute them repeatedly
59-
cache = {}
60-
root_frame = FrameInfo(("<root>", 0, "<all tasks>"))
61-
62-
def build_parent_chain(task_id, parent_name, thread_id, await_frames):
63-
"""
64-
Recursively build the parent chain for a given task by:
65-
- Finding the parent's await-site frames
66-
- Recursing up the parent chain until reaching Program Root
67-
- Add Program Root at the top of the chain
68-
- Cache results along the way to avoid redundant work
69-
"""
70-
def frame_signature(frame):
71-
func = getattr(frame, "function", None)
72-
if func is None:
73-
func = getattr(frame, "funcname", None)
74-
return (
75-
getattr(frame, "filename", None),
76-
getattr(frame, "lineno", None),
77-
func,
78-
)
79-
80-
frames_signature = tuple(
81-
frame_signature(frame) for frame in await_frames or []
82-
)
83-
cache_key = (task_id, parent_name, thread_id, frames_signature)
84-
if cache_key in cache:
85-
return cache[cache_key]
86-
87-
if not parent_name:
88-
chain = list(await_frames or []) + [root_frame]
89-
cache[cache_key] = chain
90-
return chain
91-
92-
parent_entry = None
93-
for candidate_info, candidate_tid in tasks_by_name.get(parent_name, []):
94-
if candidate_tid == thread_id:
95-
parent_entry = (candidate_info, candidate_tid)
96-
break
97-
98-
if parent_entry is None:
99-
chain = list(await_frames or []) + [root_frame]
100-
cache[cache_key] = chain
101-
return chain
102-
103-
parent_info, parent_thread = parent_entry
104-
105-
# Recursively build grandparent chain, or terminate with Program Root
106-
grandparent_chain = resolve_parent_chain(
107-
parent_info.task_id, parent_thread, parent_info.awaited_by
108-
)
109-
chain = list(await_frames or []) + grandparent_chain
110-
111-
cache[cache_key] = chain
112-
return chain
113-
114-
def resolve_parent_chain(task_id, thread_id, awaited_by_list):
115-
"""Find the best available parent chain for the given task.
116-
Best means the longest chain (most frames) among all possible parents."""
117-
best_chain = [root_frame]
118-
for coro_info in awaited_by_list or []:
119-
parent_name = coro_info.task_name
120-
await_frames = list(coro_info.call_stack or [])
121-
candidate = build_parent_chain(
122-
task_id,
123-
parent_name,
124-
thread_id,
125-
await_frames,
126-
)
127-
if len(candidate) > len(best_chain):
128-
best_chain = candidate
129-
if len(best_chain) > 1:
130-
break
131-
return best_chain
132-
133-
# Yield one complete stack per task in LEAF→ROOT order
44+
all_tasks[task_info.task_id] = (task_info, thread_id)
45+
46+
# For each task, reconstruct the full call stack by following coroutine chains
13447
for task_id, (task_info, thread_id) in all_tasks.items():
135-
# Start with the task's own body frames (deepest frames first)
136-
body_frames = [
48+
frames = [
13749
frame
138-
for coro in (task_info.coroutine_stack or [])
139-
for frame in (coro.call_stack or [])
50+
for coro in task_info.coroutine_stack
51+
for frame in coro.call_stack
14052
]
14153

142-
if task_info.awaited_by:
143-
# Add synthetic frame for the task itself
144-
task_name = task_info.task_name or f"Task-{task_id}"
145-
synthetic = FrameInfo(("<task>", 0, f"running {task_name}"))
146-
147-
# Append parent chain (await-site frames + parents recursively)
148-
parent_chain = resolve_parent_chain(
149-
task_id, thread_id, task_info.awaited_by
150-
)
151-
yield body_frames + [synthetic] + parent_chain, task_id
152-
else:
153-
# Root task: no synthetic marker needed, just add root marker
154-
yield body_frames + [root_frame], task_id
54+
task_name = task_info.task_name or f"Task-{task_id}"
55+
synthetic_frame = FrameInfo(("<task>", 0, task_name))
56+
frames.append(synthetic_frame)
57+
58+
current_parents = task_info.awaited_by
59+
visited = set()
60+
61+
while current_parents:
62+
next_parents = []
63+
for parent_coro in current_parents:
64+
frames.extend(parent_coro.call_stack)
65+
parent_task_id = parent_coro.task_name
66+
67+
if parent_task_id in visited or parent_task_id not in all_tasks:
68+
continue
69+
visited.add(parent_task_id)
70+
71+
parent_task_info, _ = all_tasks[parent_task_id]
72+
73+
parent_name = parent_task_info.task_name or f"Task-{parent_task_id}"
74+
synthetic_parent_frame = FrameInfo(("<task>", 0, parent_name))
75+
frames.append(synthetic_parent_frame)
76+
77+
if parent_task_info.awaited_by:
78+
next_parents.extend(parent_task_info.awaited_by)
79+
current_parents = next_parents
80+
yield frames, thread_id, task_id

Lib/profiling/sampling/pstats_collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _process_frames(self, frames):
4343
def collect(self, stack_frames):
4444
if stack_frames and hasattr(stack_frames[0], "awaited_by"):
4545
# Async frame processing
46-
for frames, thread_id in self._iter_async_frames(stack_frames):
46+
for frames, thread_id, task_id in self._iter_async_frames(stack_frames):
4747
self._process_frames(frames)
4848
else:
4949
# Regular frame processing

Lib/profiling/sampling/stack_collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, *, skip_idle=False):
1717
def collect(self, stack_frames, skip_idle=False):
1818
if stack_frames and hasattr(stack_frames[0], "awaited_by"):
1919
# Async-aware mode: process async task frames
20-
for frames, thread_id in self._iter_async_frames(stack_frames):
20+
for frames, thread_id, task_id in self._iter_async_frames(stack_frames):
2121
if not frames:
2222
continue
2323
self.process_frames(frames, thread_id)

0 commit comments

Comments
 (0)