mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-135953: Add GIL contention markers to sampling profiler Gecko format (#139485)
This commit enhances the Gecko format reporter in the sampling profiler to include markers for GIL acquisition events.
This commit is contained in:
parent
994ab5c922
commit
89a914c58d
9 changed files with 627 additions and 82 deletions
|
|
@ -63,12 +63,14 @@ def __repr__(self):
|
|||
class MockThreadInfo:
|
||||
"""Mock ThreadInfo for testing since the real one isn't accessible."""
|
||||
|
||||
def __init__(self, thread_id, frame_info):
|
||||
def __init__(self, thread_id, frame_info, status=0, gc_collecting=False): # Default to THREAD_STATE_RUNNING (0)
|
||||
self.thread_id = thread_id
|
||||
self.frame_info = frame_info
|
||||
self.status = status
|
||||
self.gc_collecting = gc_collecting
|
||||
|
||||
def __repr__(self):
|
||||
return f"MockThreadInfo(thread_id={self.thread_id}, frame_info={self.frame_info})"
|
||||
return f"MockThreadInfo(thread_id={self.thread_id}, frame_info={self.frame_info}, status={self.status}, gc_collecting={self.gc_collecting})"
|
||||
|
||||
|
||||
class MockInterpreterInfo:
|
||||
|
|
@ -674,6 +676,97 @@ def test_gecko_collector_export(self):
|
|||
self.assertIn("func2", string_array)
|
||||
self.assertIn("other_func", string_array)
|
||||
|
||||
def test_gecko_collector_markers(self):
|
||||
"""Test Gecko profile markers for GIL and CPU state tracking."""
|
||||
try:
|
||||
from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_GIL_REQUESTED
|
||||
except ImportError:
|
||||
THREAD_STATUS_HAS_GIL = (1 << 0)
|
||||
THREAD_STATUS_ON_CPU = (1 << 1)
|
||||
THREAD_STATUS_GIL_REQUESTED = (1 << 3)
|
||||
|
||||
collector = GeckoCollector()
|
||||
|
||||
# Status combinations for different thread states
|
||||
HAS_GIL_ON_CPU = THREAD_STATUS_HAS_GIL | THREAD_STATUS_ON_CPU # Running Python code
|
||||
NO_GIL_ON_CPU = THREAD_STATUS_ON_CPU # Running native code
|
||||
WAITING_FOR_GIL = THREAD_STATUS_GIL_REQUESTED # Waiting for GIL
|
||||
|
||||
# Simulate thread state transitions
|
||||
collector.collect([
|
||||
MockInterpreterInfo(0, [
|
||||
MockThreadInfo(1, [("test.py", 10, "python_func")], status=HAS_GIL_ON_CPU)
|
||||
])
|
||||
])
|
||||
|
||||
collector.collect([
|
||||
MockInterpreterInfo(0, [
|
||||
MockThreadInfo(1, [("test.py", 15, "wait_func")], status=WAITING_FOR_GIL)
|
||||
])
|
||||
])
|
||||
|
||||
collector.collect([
|
||||
MockInterpreterInfo(0, [
|
||||
MockThreadInfo(1, [("test.py", 20, "python_func2")], status=HAS_GIL_ON_CPU)
|
||||
])
|
||||
])
|
||||
|
||||
collector.collect([
|
||||
MockInterpreterInfo(0, [
|
||||
MockThreadInfo(1, [("native.c", 100, "native_func")], status=NO_GIL_ON_CPU)
|
||||
])
|
||||
])
|
||||
|
||||
profile_data = collector._build_profile()
|
||||
|
||||
# Verify we have threads with markers
|
||||
self.assertIn("threads", profile_data)
|
||||
self.assertEqual(len(profile_data["threads"]), 1)
|
||||
thread_data = profile_data["threads"][0]
|
||||
|
||||
# Check markers exist
|
||||
self.assertIn("markers", thread_data)
|
||||
markers = thread_data["markers"]
|
||||
|
||||
# Should have marker arrays
|
||||
self.assertIn("name", markers)
|
||||
self.assertIn("startTime", markers)
|
||||
self.assertIn("endTime", markers)
|
||||
self.assertIn("category", markers)
|
||||
self.assertGreater(markers["length"], 0, "Should have generated markers")
|
||||
|
||||
# Get marker names from string table
|
||||
string_array = profile_data["shared"]["stringArray"]
|
||||
marker_names = [string_array[idx] for idx in markers["name"]]
|
||||
|
||||
# Verify we have different marker types
|
||||
marker_name_set = set(marker_names)
|
||||
|
||||
# Should have "Has GIL" markers (when thread had GIL)
|
||||
self.assertIn("Has GIL", marker_name_set, "Should have 'Has GIL' markers")
|
||||
|
||||
# Should have "No GIL" markers (when thread didn't have GIL)
|
||||
self.assertIn("No GIL", marker_name_set, "Should have 'No GIL' markers")
|
||||
|
||||
# Should have "On CPU" markers (when thread was on CPU)
|
||||
self.assertIn("On CPU", marker_name_set, "Should have 'On CPU' markers")
|
||||
|
||||
# Should have "Waiting for GIL" markers (when thread was waiting)
|
||||
self.assertIn("Waiting for GIL", marker_name_set, "Should have 'Waiting for GIL' markers")
|
||||
|
||||
# Verify marker structure
|
||||
for i in range(markers["length"]):
|
||||
# All markers should be interval markers (phase = 1)
|
||||
self.assertEqual(markers["phase"][i], 1, f"Marker {i} should be interval marker")
|
||||
|
||||
# All markers should have valid time range
|
||||
start_time = markers["startTime"][i]
|
||||
end_time = markers["endTime"][i]
|
||||
self.assertLessEqual(start_time, end_time, f"Marker {i} should have valid time range")
|
||||
|
||||
# All markers should have valid category
|
||||
self.assertGreaterEqual(markers["category"][i], 0, f"Marker {i} should have valid category")
|
||||
|
||||
def test_pstats_collector_export(self):
|
||||
collector = PstatsCollector(
|
||||
sample_interval_usec=1000000
|
||||
|
|
@ -2625,19 +2718,30 @@ def test_mode_validation(self):
|
|||
|
||||
def test_frames_filtered_with_skip_idle(self):
|
||||
"""Test that frames are actually filtered when skip_idle=True."""
|
||||
# Import thread status flags
|
||||
try:
|
||||
from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU
|
||||
except ImportError:
|
||||
THREAD_STATUS_HAS_GIL = (1 << 0)
|
||||
THREAD_STATUS_ON_CPU = (1 << 1)
|
||||
|
||||
# Create mock frames with different thread statuses
|
||||
class MockThreadInfoWithStatus:
|
||||
def __init__(self, thread_id, frame_info, status):
|
||||
self.thread_id = thread_id
|
||||
self.frame_info = frame_info
|
||||
self.status = status
|
||||
self.gc_collecting = False
|
||||
|
||||
# Create test data: active thread (HAS_GIL | ON_CPU), idle thread (neither), and another active thread
|
||||
ACTIVE_STATUS = THREAD_STATUS_HAS_GIL | THREAD_STATUS_ON_CPU # Has GIL and on CPU
|
||||
IDLE_STATUS = 0 # Neither has GIL nor on CPU
|
||||
|
||||
# Create test data: running thread, idle thread, and another running thread
|
||||
test_frames = [
|
||||
MockInterpreterInfo(0, [
|
||||
MockThreadInfoWithStatus(1, [MockFrameInfo("active1.py", 10, "active_func1")], 0), # RUNNING
|
||||
MockThreadInfoWithStatus(2, [MockFrameInfo("idle.py", 20, "idle_func")], 1), # IDLE
|
||||
MockThreadInfoWithStatus(3, [MockFrameInfo("active2.py", 30, "active_func2")], 0), # RUNNING
|
||||
MockThreadInfoWithStatus(1, [MockFrameInfo("active1.py", 10, "active_func1")], ACTIVE_STATUS),
|
||||
MockThreadInfoWithStatus(2, [MockFrameInfo("idle.py", 20, "idle_func")], IDLE_STATUS),
|
||||
MockThreadInfoWithStatus(3, [MockFrameInfo("active2.py", 30, "active_func2")], ACTIVE_STATUS),
|
||||
])
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue