Skip to content

Commit e739702

Browse files
committed
Fix GC markers
1 parent a332095 commit e739702

File tree

1 file changed

+18
-43
lines changed

1 file changed

+18
-43
lines changed

Lib/profiling/sampling/gecko_collector.py

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ def __init__(self, *, skip_idle=False):
8484
self.native_code_start = {} # Thread running native code (on CPU without GIL)
8585
self.gil_wait_start = {} # Thread waiting for GIL
8686

87-
# GC event tracking: track if we're currently in a GC
88-
self.potential_gc_start = None
87+
# GC event tracking: track GC start time per thread
88+
self.gc_start_per_thread = {} # tid -> start_time
8989

9090
# Track which threads have been initialized for state tracking
9191
self.initialized_threads = set()
@@ -136,17 +136,12 @@ def collect(self, stack_frames):
136136
) / self.sample_count
137137
self.last_sample_time = current_time
138138

139-
# GC Event Detection and process threads
140-
gc_collecting = False
141-
139+
# Process threads and track GC per thread
142140
for interpreter_info in stack_frames:
143141
for thread_info in interpreter_info.threads:
144-
# Track GC status
145-
if thread_info.gc_collecting:
146-
gc_collecting = True
147-
148142
frames = thread_info.frame_info
149143
tid = thread_info.thread_id
144+
gc_collecting = thread_info.gc_collecting
150145

151146
# Initialize thread if needed
152147
if tid not in self.threads:
@@ -202,6 +197,19 @@ def collect(self, stack_frames):
202197
self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid),
203198
current_time, CATEGORY_GIL)
204199

200+
# Track GC events - attribute to all threads that hold the GIL during GC
201+
# (GC is interpreter-wide but runs on whichever thread(s) have the GIL)
202+
# If GIL switches during GC, multiple threads will get GC markers
203+
if gc_collecting and has_gil:
204+
# Start GC marker if not already started for this thread
205+
if tid not in self.gc_start_per_thread:
206+
self.gc_start_per_thread[tid] = current_time
207+
elif tid in self.gc_start_per_thread:
208+
# End GC marker if it was running for this thread
209+
# (either GC finished or thread lost GIL)
210+
self._add_marker(tid, "GC Collecting", self.gc_start_per_thread.pop(tid),
211+
current_time, CATEGORY_GC)
212+
205213
# Mark thread as initialized after processing all state transitions
206214
self.initialized_threads.add(tid)
207215

@@ -224,17 +232,6 @@ def collect(self, stack_frames):
224232
samples["time"].append(current_time)
225233
samples["eventDelay"].append(None)
226234

227-
# Handle GC event markers after processing all threads
228-
if gc_collecting:
229-
if self.potential_gc_start is None:
230-
# Start of GC
231-
self.potential_gc_start = current_time
232-
else:
233-
# End of GC
234-
if self.potential_gc_start is not None:
235-
self._add_gc_marker(self.potential_gc_start, current_time)
236-
self.potential_gc_start = None
237-
238235
self.sample_count += 1
239236

240237
def _create_thread(self, tid):
@@ -372,24 +369,6 @@ def _add_marker(self, tid, name, start_time, end_time, category):
372369
"tid": tid
373370
})
374371

375-
def _add_gc_marker(self, start_time, end_time):
376-
"""Add a GC Collecting event marker to the main thread."""
377-
if not self.threads:
378-
return
379-
380-
# Find the main thread by checking isMainThread flag
381-
main_tid = None
382-
for tid, thread_data in self.threads.items():
383-
if thread_data.get("isMainThread", False):
384-
main_tid = tid
385-
break
386-
387-
# If we can't find the main thread, use the first thread as fallback
388-
if main_tid is None:
389-
main_tid = next(iter(self.threads))
390-
391-
self._add_marker(main_tid, "GC Collecting", start_time, end_time, CATEGORY_GC)
392-
393372
def _process_stack(self, thread_data, frames):
394373
"""Process a stack and return the stack index."""
395374
if not frames:
@@ -571,18 +550,14 @@ def _finalize_markers(self):
571550
(self.python_code_start, "Python Code", CATEGORY_CODE_TYPE),
572551
(self.native_code_start, "Native Code", CATEGORY_CODE_TYPE),
573552
(self.gil_wait_start, "Waiting for GIL", CATEGORY_GIL),
553+
(self.gc_start_per_thread, "GC Collecting", CATEGORY_GC),
574554
]
575555

576556
for state_dict, marker_name, category in marker_states:
577557
for tid in list(state_dict.keys()):
578558
self._add_marker(tid, marker_name, state_dict[tid], end_time, category)
579559
del state_dict[tid]
580560

581-
# Close any open GC marker
582-
if self.potential_gc_start is not None:
583-
self._add_gc_marker(self.potential_gc_start, end_time)
584-
self.potential_gc_start = None
585-
586561
def export(self, filename):
587562
"""Export the profile to a Gecko JSON file."""
588563

0 commit comments

Comments
 (0)