|
8 | 8 |
|
9 | 9 | from .collector import Collector |
10 | 10 | try: |
11 | | - from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED |
| 11 | + from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED, THREAD_STATUS_HAS_EXCEPTION, THREAD_STATUS_IN_CRITICAL_SECTION |
12 | 12 | except ImportError: |
13 | 13 | # Fallback if module not available (shouldn't happen in normal use) |
14 | 14 | THREAD_STATUS_HAS_GIL = (1 << 0) |
15 | 15 | THREAD_STATUS_ON_CPU = (1 << 1) |
16 | 16 | THREAD_STATUS_UNKNOWN = (1 << 2) |
17 | 17 | THREAD_STATUS_GIL_REQUESTED = (1 << 3) |
| 18 | + THREAD_STATUS_HAS_EXCEPTION = (1 << 4) |
| 19 | + THREAD_STATUS_IN_CRITICAL_SECTION = (1 << 5) |
18 | 20 |
|
19 | 21 |
|
20 | 22 | # Categories matching Firefox Profiler expectations |
|
26 | 28 | {"name": "GIL", "color": "green", "subcategories": ["Other"]}, |
27 | 29 | {"name": "CPU", "color": "purple", "subcategories": ["Other"]}, |
28 | 30 | {"name": "Code Type", "color": "red", "subcategories": ["Other"]}, |
| 31 | + {"name": "Exception", "color": "magenta", "subcategories": ["Other"]}, |
| 32 | + {"name": "Critical Section", "color": "lightblue", "subcategories": ["Other"]}, |
29 | 33 | ] |
30 | 34 |
|
31 | 35 | # Category indices |
|
36 | 40 | CATEGORY_GIL = 4 |
37 | 41 | CATEGORY_CPU = 5 |
38 | 42 | CATEGORY_CODE_TYPE = 6 |
| 43 | +CATEGORY_EXCEPTION = 7 |
| 44 | +CATEGORY_CRITICAL_SECTION = 8 |
39 | 45 |
|
40 | 46 | # Subcategory indices |
41 | 47 | DEFAULT_SUBCATEGORY = 0 |
@@ -84,6 +90,10 @@ def __init__(self, sample_interval_usec, *, skip_idle=False): |
84 | 90 | self.python_code_start = {} # Thread running Python code (has GIL) |
85 | 91 | self.native_code_start = {} # Thread running native code (on CPU without GIL) |
86 | 92 | self.gil_wait_start = {} # Thread waiting for GIL |
| 93 | + self.exception_start = {} # Thread has an exception set |
| 94 | + self.no_exception_start = {} # Thread has no exception set |
| 95 | + self.critical_section_start = {} # Thread is in critical section (free-threaded) |
| 96 | + self.no_critical_section_start = {} # Thread is not in critical section |
87 | 97 |
|
88 | 98 | # GC event tracking: track GC start time per thread |
89 | 99 | self.gc_start_per_thread = {} # tid -> start_time |
@@ -197,6 +207,21 @@ def collect(self, stack_frames): |
197 | 207 | self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid), |
198 | 208 | current_time, CATEGORY_GIL) |
199 | 209 |
|
| 210 | + # Track exception state (Has Exception / No Exception) |
| 211 | + has_exception = bool(status_flags & THREAD_STATUS_HAS_EXCEPTION) |
| 212 | + self._track_state_transition( |
| 213 | + tid, has_exception, self.exception_start, self.no_exception_start, |
| 214 | + "Has Exception", "No Exception", CATEGORY_EXCEPTION, current_time |
| 215 | + ) |
| 216 | + |
| 217 | + # Track critical section state (In Critical Section / Not In Critical Section) |
| 218 | + # This is mainly relevant for free-threaded Python builds |
| 219 | + in_critical_section = bool(status_flags & THREAD_STATUS_IN_CRITICAL_SECTION) |
| 220 | + self._track_state_transition( |
| 221 | + tid, in_critical_section, self.critical_section_start, self.no_critical_section_start, |
| 222 | + "In Critical Section", "Not In Critical Section", CATEGORY_CRITICAL_SECTION, current_time |
| 223 | + ) |
| 224 | + |
200 | 225 | # Track GC events by detecting <GC> frames in the stack trace |
201 | 226 | # This leverages the improved GC frame tracking from commit 336366fd7ca |
202 | 227 | # which precisely identifies the thread that initiated GC collection |
@@ -551,6 +576,10 @@ def _finalize_markers(self): |
551 | 576 | (self.native_code_start, "Native Code", CATEGORY_CODE_TYPE), |
552 | 577 | (self.gil_wait_start, "Waiting for GIL", CATEGORY_GIL), |
553 | 578 | (self.gc_start_per_thread, "GC Collecting", CATEGORY_GC), |
| 579 | + (self.exception_start, "Has Exception", CATEGORY_EXCEPTION), |
| 580 | + (self.no_exception_start, "No Exception", CATEGORY_EXCEPTION), |
| 581 | + (self.critical_section_start, "In Critical Section", CATEGORY_CRITICAL_SECTION), |
| 582 | + (self.no_critical_section_start, "Not In Critical Section", CATEGORY_CRITICAL_SECTION), |
554 | 583 | ] |
555 | 584 |
|
556 | 585 | for state_dict, marker_name, category in marker_states: |
|
0 commit comments