@@ -244,6 +244,12 @@ enum _ThreadState {
244
244
THREAD_STATE_UNKNOWN
245
245
};
246
246
247
+ enum _ProfilingMode {
248
+ PROFILING_MODE_WALL = 0 ,
249
+ PROFILING_MODE_CPU = 1 ,
250
+ PROFILING_MODE_GIL = 2
251
+ };
252
+
247
253
typedef struct {
248
254
PyObject_HEAD
249
255
proc_handle_t handle ;
@@ -257,7 +263,7 @@ typedef struct {
257
263
_Py_hashtable_t * code_object_cache ;
258
264
int debug ;
259
265
int only_active_thread ;
260
- int cpu_time ;
266
+ int mode ; // Use enum _ProfilingMode values
261
267
RemoteDebuggingState * cached_state ; // Cached module state
262
268
#ifdef Py_GIL_DISABLED
263
269
// TLBC cache invalidation tracking
@@ -2629,6 +2635,39 @@ unwind_stack_for_thread(
2629
2635
goto error ;
2630
2636
}
2631
2637
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
+
2632
2671
uintptr_t frame_addr = GET_MEMBER (uintptr_t , ts , unwinder -> debug_offsets .thread_state .current_frame );
2633
2672
2634
2673
frame_info = PyList_New (0 );
@@ -2642,20 +2681,6 @@ unwind_stack_for_thread(
2642
2681
goto error ;
2643
2682
}
2644
2683
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
-
2659
2684
if (process_frame_chain (unwinder , frame_addr , & chunks , frame_info ) < 0 ) {
2660
2685
set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to process frame chain" );
2661
2686
goto error ;
@@ -2716,7 +2741,7 @@ _remote_debugging.RemoteUnwinder.__init__
2716
2741
*
2717
2742
all_threads: bool = False
2718
2743
only_active_thread: bool = False
2719
- cpu_time: bool = False
2744
+ mode: int = 0
2720
2745
debug: bool = False
2721
2746
2722
2747
Initialize 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.
2726
2751
all_threads: If True, initialize state for all threads in the process.
2727
2752
If False, only initialize for the main thread.
2728
2753
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) .
2730
2755
Cannot be used together with all_threads=True.
2731
2756
debug: If True, chain exceptions to explain the sequence of events that
2732
2757
lead to the exception.
@@ -2745,8 +2770,8 @@ static int
2745
2770
_remote_debugging_RemoteUnwinder___init___impl (RemoteUnwinderObject * self ,
2746
2771
int pid , int all_threads ,
2747
2772
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 ]*/
2750
2775
{
2751
2776
// Validate that all_threads and only_active_thread are not both True
2752
2777
if (all_threads && only_active_thread ) {
@@ -2765,7 +2790,7 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
2765
2790
2766
2791
self -> debug = debug ;
2767
2792
self -> only_active_thread = only_active_thread ;
2768
- self -> cpu_time = cpu_time ;
2793
+ self -> mode = mode ;
2769
2794
self -> cached_state = NULL ;
2770
2795
if (_Py_RemoteDebug_InitProcHandle (& self -> handle , pid ) < 0 ) {
2771
2796
set_exception_cause (self , PyExc_RuntimeError , "Failed to initialize process handle" );
@@ -2983,6 +3008,12 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
2983
3008
while (current_tstate != 0 ) {
2984
3009
PyObject * frame_info = unwind_stack_for_thread (self , & current_tstate , gil_holder_tstate );
2985
3010
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
2986
3017
Py_DECREF (interpreter_threads );
2987
3018
set_exception_cause (self , PyExc_RuntimeError , "Failed to unwind stack for thread" );
2988
3019
Py_CLEAR (result );
0 commit comments