1515from .stack_collector import CollapsedStackCollector , FlamegraphCollector
1616
1717_FREE_THREADED_BUILD = sysconfig .get_config_var ("Py_GIL_DISABLED" ) is not None
18+
19+ # Profiling mode constants
20+ PROFILING_MODE_WALL = 0
21+ PROFILING_MODE_CPU = 1
22+ PROFILING_MODE_GIL = 2
23+
24+
25+ def _parse_mode (mode_string ):
26+ """Convert mode string to mode constant."""
27+ mode_map = {
28+ "wall" : PROFILING_MODE_WALL ,
29+ "cpu" : PROFILING_MODE_CPU ,
30+ "gil" : PROFILING_MODE_GIL ,
31+ }
32+ return mode_map [mode_string ]
1833_HELP_DESCRIPTION = """Sample a process's stack frames and generate profiling data.
1934Supports the following target modes:
2035 - -p PID: Profile an existing process by PID
@@ -120,18 +135,18 @@ def _run_with_sync(original_cmd):
120135
121136
122137class SampleProfiler :
123- def __init__ (self , pid , sample_interval_usec , all_threads ):
138+ def __init__ (self , pid , sample_interval_usec , all_threads , * , mode = PROFILING_MODE_WALL ):
124139 self .pid = pid
125140 self .sample_interval_usec = sample_interval_usec
126141 self .all_threads = all_threads
127142 if _FREE_THREADED_BUILD :
128143 self .unwinder = _remote_debugging .RemoteUnwinder (
129- self .pid , all_threads = self .all_threads
144+ self .pid , all_threads = self .all_threads , mode = mode
130145 )
131146 else :
132147 only_active_threads = bool (self .all_threads )
133148 self .unwinder = _remote_debugging .RemoteUnwinder (
134- self .pid , only_active_thread = only_active_threads
149+ self .pid , only_active_thread = only_active_threads , mode = mode
135150 )
136151 # Track sample intervals and total sample count
137152 self .sample_intervals = deque (maxlen = 100 )
@@ -596,21 +611,25 @@ def sample(
596611 show_summary = True ,
597612 output_format = "pstats" ,
598613 realtime_stats = False ,
614+ mode = PROFILING_MODE_WALL ,
599615):
600616 profiler = SampleProfiler (
601- pid , sample_interval_usec , all_threads = all_threads
617+ pid , sample_interval_usec , all_threads = all_threads , mode = mode
602618 )
603619 profiler .realtime_stats = realtime_stats
604620
621+ # Determine skip_idle for collector compatibility
622+ skip_idle = mode != PROFILING_MODE_WALL
623+
605624 collector = None
606625 match output_format :
607626 case "pstats" :
608- collector = PstatsCollector (sample_interval_usec )
627+ collector = PstatsCollector (sample_interval_usec , skip_idle = skip_idle )
609628 case "collapsed" :
610- collector = CollapsedStackCollector ()
629+ collector = CollapsedStackCollector (skip_idle = skip_idle )
611630 filename = filename or f"collapsed.{ pid } .txt"
612631 case "flamegraph" :
613- collector = FlamegraphCollector ()
632+ collector = FlamegraphCollector (skip_idle = skip_idle )
614633 filename = filename or f"flamegraph.{ pid } .html"
615634 case _:
616635 raise ValueError (f"Invalid output format: { output_format } " )
@@ -661,6 +680,8 @@ def wait_for_process_and_sample(pid, sort_value, args):
661680 if not filename and args .format == "collapsed" :
662681 filename = f"collapsed.{ pid } .txt"
663682
683+ mode = _parse_mode (args .mode )
684+
664685 sample (
665686 pid ,
666687 sort = sort_value ,
@@ -672,6 +693,7 @@ def wait_for_process_and_sample(pid, sort_value, args):
672693 show_summary = not args .no_summary ,
673694 output_format = args .format ,
674695 realtime_stats = args .realtime_stats ,
696+ mode = mode ,
675697 )
676698
677699
@@ -726,6 +748,15 @@ def main():
726748 help = "Print real-time sampling statistics (Hz, mean, min, max, stdev) during profiling" ,
727749 )
728750
751+ # Mode options
752+ mode_group = parser .add_argument_group ("Mode options" )
753+ mode_group .add_argument (
754+ "--mode" ,
755+ choices = ["wall" , "cpu" , "gil" ],
756+ default = "wall" ,
757+ help = "Sampling mode: wall (all threads), cpu (only CPU-running threads), gil (only GIL-holding threads)" ,
758+ )
759+
729760 # Output format selection
730761 output_group = parser .add_argument_group ("Output options" )
731762 output_format = output_group .add_mutually_exclusive_group ()
@@ -850,6 +881,8 @@ def main():
850881 elif target_count > 1 :
851882 parser .error ("only one target type can be specified: -p/--pid, -m/--module, or script" )
852883
884+ mode = _parse_mode (args .mode )
885+
853886 if args .pid :
854887 sample (
855888 args .pid ,
@@ -862,6 +895,7 @@ def main():
862895 show_summary = not args .no_summary ,
863896 output_format = args .format ,
864897 realtime_stats = args .realtime_stats ,
898+ mode = mode ,
865899 )
866900 elif args .module or args .args :
867901 if args .module :
0 commit comments