15
15
from .stack_collector import CollapsedStackCollector , FlamegraphCollector
16
16
17
17
_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 ]
18
33
_HELP_DESCRIPTION = """Sample a process's stack frames and generate profiling data.
19
34
Supports the following target modes:
20
35
- -p PID: Profile an existing process by PID
@@ -120,18 +135,18 @@ def _run_with_sync(original_cmd):
120
135
121
136
122
137
class 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 ):
124
139
self .pid = pid
125
140
self .sample_interval_usec = sample_interval_usec
126
141
self .all_threads = all_threads
127
142
if _FREE_THREADED_BUILD :
128
143
self .unwinder = _remote_debugging .RemoteUnwinder (
129
- self .pid , all_threads = self .all_threads
144
+ self .pid , all_threads = self .all_threads , mode = mode
130
145
)
131
146
else :
132
147
only_active_threads = bool (self .all_threads )
133
148
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
135
150
)
136
151
# Track sample intervals and total sample count
137
152
self .sample_intervals = deque (maxlen = 100 )
@@ -596,21 +611,25 @@ def sample(
596
611
show_summary = True ,
597
612
output_format = "pstats" ,
598
613
realtime_stats = False ,
614
+ mode = PROFILING_MODE_WALL ,
599
615
):
600
616
profiler = SampleProfiler (
601
- pid , sample_interval_usec , all_threads = all_threads
617
+ pid , sample_interval_usec , all_threads = all_threads , mode = mode
602
618
)
603
619
profiler .realtime_stats = realtime_stats
604
620
621
+ # Determine skip_idle for collector compatibility
622
+ skip_idle = mode != PROFILING_MODE_WALL
623
+
605
624
collector = None
606
625
match output_format :
607
626
case "pstats" :
608
- collector = PstatsCollector (sample_interval_usec )
627
+ collector = PstatsCollector (sample_interval_usec , skip_idle = skip_idle )
609
628
case "collapsed" :
610
- collector = CollapsedStackCollector ()
629
+ collector = CollapsedStackCollector (skip_idle = skip_idle )
611
630
filename = filename or f"collapsed.{ pid } .txt"
612
631
case "flamegraph" :
613
- collector = FlamegraphCollector ()
632
+ collector = FlamegraphCollector (skip_idle = skip_idle )
614
633
filename = filename or f"flamegraph.{ pid } .html"
615
634
case _:
616
635
raise ValueError (f"Invalid output format: { output_format } " )
@@ -661,6 +680,8 @@ def wait_for_process_and_sample(pid, sort_value, args):
661
680
if not filename and args .format == "collapsed" :
662
681
filename = f"collapsed.{ pid } .txt"
663
682
683
+ mode = _parse_mode (args .mode )
684
+
664
685
sample (
665
686
pid ,
666
687
sort = sort_value ,
@@ -672,6 +693,7 @@ def wait_for_process_and_sample(pid, sort_value, args):
672
693
show_summary = not args .no_summary ,
673
694
output_format = args .format ,
674
695
realtime_stats = args .realtime_stats ,
696
+ mode = mode ,
675
697
)
676
698
677
699
@@ -726,6 +748,15 @@ def main():
726
748
help = "Print real-time sampling statistics (Hz, mean, min, max, stdev) during profiling" ,
727
749
)
728
750
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
+
729
760
# Output format selection
730
761
output_group = parser .add_argument_group ("Output options" )
731
762
output_format = output_group .add_mutually_exclusive_group ()
@@ -850,6 +881,8 @@ def main():
850
881
elif target_count > 1 :
851
882
parser .error ("only one target type can be specified: -p/--pid, -m/--module, or script" )
852
883
884
+ mode = _parse_mode (args .mode )
885
+
853
886
if args .pid :
854
887
sample (
855
888
args .pid ,
@@ -862,6 +895,7 @@ def main():
862
895
show_summary = not args .no_summary ,
863
896
output_format = args .format ,
864
897
realtime_stats = args .realtime_stats ,
898
+ mode = mode ,
865
899
)
866
900
elif args .module or args .args :
867
901
if args .module :
0 commit comments