@@ -244,6 +244,12 @@ enum _ThreadState {
244244 THREAD_STATE_UNKNOWN
245245};
246246
247+ enum _ProfilingMode {
248+ PROFILING_MODE_WALL = 0 ,
249+ PROFILING_MODE_CPU = 1 ,
250+ PROFILING_MODE_GIL = 2
251+ };
252+
247253typedef struct {
248254 PyObject_HEAD
249255 proc_handle_t handle ;
@@ -257,7 +263,7 @@ typedef struct {
257263 _Py_hashtable_t * code_object_cache ;
258264 int debug ;
259265 int only_active_thread ;
260- int cpu_time ;
266+ int mode ; // Use enum _ProfilingMode values
261267 RemoteDebuggingState * cached_state ; // Cached module state
262268#ifdef Py_GIL_DISABLED
263269 // TLBC cache invalidation tracking
@@ -2629,6 +2635,39 @@ unwind_stack_for_thread(
26292635 goto error ;
26302636 }
26312637
2638+ long tid = GET_MEMBER (long , ts , unwinder -> debug_offsets .thread_state .native_thread_id );
2639+
2640+ // Calculate thread status based on mode
2641+ int status = THREAD_STATE_UNKNOWN ;
2642+ if (unwinder -> mode == PROFILING_MODE_CPU ) {
2643+ long pthread_id = GET_MEMBER (long , ts , unwinder -> debug_offsets .thread_state .thread_id );
2644+ status = get_thread_status (unwinder , tid , pthread_id );
2645+ if (status == -1 ) {
2646+ PyErr_Print ();
2647+ PyErr_SetString (PyExc_RuntimeError , "Failed to get thread status" );
2648+ goto error ;
2649+ }
2650+ } else if (unwinder -> mode == PROFILING_MODE_GIL ) {
2651+ status = (* current_tstate == gil_holder_tstate ) ? THREAD_STATE_RUNNING : THREAD_STATE_GIL_WAIT ;
2652+ } else {
2653+ // PROFILING_MODE_WALL - all threads are considered running
2654+ status = THREAD_STATE_RUNNING ;
2655+ }
2656+
2657+ // Check if we should skip this thread based on mode
2658+ int should_skip = 0 ;
2659+ if (unwinder -> mode == PROFILING_MODE_CPU && status != THREAD_STATE_RUNNING ) {
2660+ should_skip = 1 ;
2661+ } else if (unwinder -> mode == PROFILING_MODE_GIL && status != THREAD_STATE_RUNNING ) {
2662+ should_skip = 1 ;
2663+ }
2664+
2665+ if (should_skip ) {
2666+ // Advance to next thread and return NULL to skip processing
2667+ * current_tstate = GET_MEMBER (uintptr_t , ts , unwinder -> debug_offsets .thread_state .next );
2668+ return NULL ;
2669+ }
2670+
26322671 uintptr_t frame_addr = GET_MEMBER (uintptr_t , ts , unwinder -> debug_offsets .thread_state .current_frame );
26332672
26342673 frame_info = PyList_New (0 );
@@ -2642,20 +2681,6 @@ unwind_stack_for_thread(
26422681 goto error ;
26432682 }
26442683
2645- long tid = GET_MEMBER (long , ts , unwinder -> debug_offsets .thread_state .native_thread_id );
2646- int status = THREAD_STATE_UNKNOWN ;
2647- if (unwinder -> cpu_time == 1 ) {
2648- long pthread_id = GET_MEMBER (long , ts , unwinder -> debug_offsets .thread_state .thread_id );
2649- status = get_thread_status (unwinder , tid , pthread_id );
2650- if (status == -1 ) {
2651- PyErr_Print ();
2652- PyErr_SetString (PyExc_RuntimeError , "Failed to get thread status" );
2653- goto error ;
2654- }
2655- } else {
2656- status = (* current_tstate == gil_holder_tstate ) ? THREAD_STATE_RUNNING : THREAD_STATE_GIL_WAIT ;
2657- }
2658-
26592684 if (process_frame_chain (unwinder , frame_addr , & chunks , frame_info ) < 0 ) {
26602685 set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to process frame chain" );
26612686 goto error ;
@@ -2716,7 +2741,7 @@ _remote_debugging.RemoteUnwinder.__init__
27162741 *
27172742 all_threads: bool = False
27182743 only_active_thread: bool = False
2719- cpu_time: bool = False
2744+ mode: int = 0
27202745 debug: bool = False
27212746
27222747Initialize a new RemoteUnwinder object for debugging a remote Python process.
@@ -2726,7 +2751,7 @@ Initialize a new RemoteUnwinder object for debugging a remote Python process.
27262751 all_threads: If True, initialize state for all threads in the process.
27272752 If False, only initialize for the main thread.
27282753 only_active_thread: If True, only sample the thread holding the GIL.
2729- cpu_time: If True, enable CPU time tracking for unwinder operations .
2754+ mode: Profiling mode: 0=WALL (wall-time), 1= CPU (cpu- time), 2=GIL (gil-time) .
27302755 Cannot be used together with all_threads=True.
27312756 debug: If True, chain exceptions to explain the sequence of events that
27322757 lead to the exception.
@@ -2745,8 +2770,8 @@ static int
27452770_remote_debugging_RemoteUnwinder___init___impl (RemoteUnwinderObject * self ,
27462771 int pid , int all_threads ,
27472772 int only_active_thread ,
2748- int cpu_time , int debug )
2749- /*[clinic end generated code: output=2598ce54f6335ac7 input=0cf2038cc304c165 ]*/
2773+ int mode , int debug )
2774+ /*[clinic end generated code: output=784e9990115aa569 input=d082d792d2ba9924 ]*/
27502775{
27512776 // Validate that all_threads and only_active_thread are not both True
27522777 if (all_threads && only_active_thread ) {
@@ -2765,7 +2790,7 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
27652790
27662791 self -> debug = debug ;
27672792 self -> only_active_thread = only_active_thread ;
2768- self -> cpu_time = cpu_time ;
2793+ self -> mode = mode ;
27692794 self -> cached_state = NULL ;
27702795 if (_Py_RemoteDebug_InitProcHandle (& self -> handle , pid ) < 0 ) {
27712796 set_exception_cause (self , PyExc_RuntimeError , "Failed to initialize process handle" );
@@ -2983,6 +3008,12 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
29833008 while (current_tstate != 0 ) {
29843009 PyObject * frame_info = unwind_stack_for_thread (self , & current_tstate , gil_holder_tstate );
29853010 if (!frame_info ) {
3011+ // Check if this was an intentional skip due to mode-based filtering
3012+ if ((self -> mode == PROFILING_MODE_CPU || self -> mode == PROFILING_MODE_GIL ) && !PyErr_Occurred ()) {
3013+ // Thread was skipped due to mode filtering, continue to next thread
3014+ continue ;
3015+ }
3016+ // This was an actual error
29863017 Py_DECREF (interpreter_threads );
29873018 set_exception_cause (self , PyExc_RuntimeError , "Failed to unwind stack for thread" );
29883019 Py_CLEAR (result );
0 commit comments