|
9 | 9 | from .collector import Collector |
10 | 10 | from .opcode_utils import get_opcode_info, format_opcode |
11 | 11 | try: |
12 | | - from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED |
| 12 | + from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED, THREAD_STATUS_HAS_EXCEPTION |
13 | 13 | except ImportError: |
14 | 14 | # Fallback if module not available (shouldn't happen in normal use) |
15 | 15 | THREAD_STATUS_HAS_GIL = (1 << 0) |
16 | 16 | THREAD_STATUS_ON_CPU = (1 << 1) |
17 | 17 | THREAD_STATUS_UNKNOWN = (1 << 2) |
18 | 18 | THREAD_STATUS_GIL_REQUESTED = (1 << 3) |
| 19 | + THREAD_STATUS_HAS_EXCEPTION = (1 << 4) |
19 | 20 |
|
20 | 21 |
|
21 | 22 | # Categories matching Firefox Profiler expectations |
|
28 | 29 | {"name": "CPU", "color": "purple", "subcategories": ["Other"]}, |
29 | 30 | {"name": "Code Type", "color": "red", "subcategories": ["Other"]}, |
30 | 31 | {"name": "Opcodes", "color": "magenta", "subcategories": ["Other"]}, |
| 32 | + {"name": "Exception", "color": "lightblue", "subcategories": ["Other"]}, |
31 | 33 | ] |
32 | 34 |
|
33 | 35 | # Category indices |
|
39 | 41 | CATEGORY_CPU = 5 |
40 | 42 | CATEGORY_CODE_TYPE = 6 |
41 | 43 | CATEGORY_OPCODES = 7 |
| 44 | +CATEGORY_EXCEPTION = 8 |
42 | 45 |
|
43 | 46 | # Subcategory indices |
44 | 47 | DEFAULT_SUBCATEGORY = 0 |
@@ -88,6 +91,8 @@ def __init__(self, sample_interval_usec, *, skip_idle=False, opcodes=False): |
88 | 91 | self.python_code_start = {} # Thread running Python code (has GIL) |
89 | 92 | self.native_code_start = {} # Thread running native code (on CPU without GIL) |
90 | 93 | self.gil_wait_start = {} # Thread waiting for GIL |
| 94 | + self.exception_start = {} # Thread has an exception set |
| 95 | + self.no_exception_start = {} # Thread has no exception set |
91 | 96 |
|
92 | 97 | # GC event tracking: track GC start time per thread |
93 | 98 | self.gc_start_per_thread = {} # tid -> start_time |
@@ -204,6 +209,13 @@ def collect(self, stack_frames): |
204 | 209 | self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid), |
205 | 210 | current_time, CATEGORY_GIL) |
206 | 211 |
|
| 212 | + # Track exception state (Has Exception / No Exception) |
| 213 | + has_exception = bool(status_flags & THREAD_STATUS_HAS_EXCEPTION) |
| 214 | + self._track_state_transition( |
| 215 | + tid, has_exception, self.exception_start, self.no_exception_start, |
| 216 | + "Has Exception", "No Exception", CATEGORY_EXCEPTION, current_time |
| 217 | + ) |
| 218 | + |
207 | 219 | # Track GC events by detecting <GC> frames in the stack trace |
208 | 220 | # This leverages the improved GC frame tracking from commit 336366fd7ca |
209 | 221 | # which precisely identifies the thread that initiated GC collection |
@@ -622,6 +634,8 @@ def _finalize_markers(self): |
622 | 634 | (self.native_code_start, "Native Code", CATEGORY_CODE_TYPE), |
623 | 635 | (self.gil_wait_start, "Waiting for GIL", CATEGORY_GIL), |
624 | 636 | (self.gc_start_per_thread, "GC Collecting", CATEGORY_GC), |
| 637 | + (self.exception_start, "Has Exception", CATEGORY_EXCEPTION), |
| 638 | + (self.no_exception_start, "No Exception", CATEGORY_EXCEPTION), |
625 | 639 | ] |
626 | 640 |
|
627 | 641 | for state_dict, marker_name, category in marker_states: |
|
0 commit comments