@@ -87,6 +87,31 @@ def __init__(self, *, skip_idle=False):
87
87
# GC event tracking: track if we're currently in a GC
88
88
self .potential_gc_start = None
89
89
90
+ def _track_state_transition (self , tid , condition , active_dict , inactive_dict ,
91
+ active_name , inactive_name , category , current_time ):
92
+ """Track binary state transitions and emit markers.
93
+
94
+ Args:
95
+ tid: Thread ID
96
+ condition: Whether the active state is true
97
+ active_dict: Dict tracking start time of active state
98
+ inactive_dict: Dict tracking start time of inactive state
99
+ active_name: Name for active state marker
100
+ inactive_name: Name for inactive state marker
101
+ category: Gecko category for the markers
102
+ current_time: Current timestamp
103
+ """
104
+ if condition :
105
+ active_dict .setdefault (tid , current_time )
106
+ if tid in inactive_dict :
107
+ self ._add_marker (tid , inactive_name , inactive_dict .pop (tid ),
108
+ current_time , category )
109
+ else :
110
+ inactive_dict .setdefault (tid , current_time )
111
+ if tid in active_dict :
112
+ self ._add_marker (tid , active_name , active_dict .pop (tid ),
113
+ current_time , category )
114
+
90
115
def collect (self , stack_frames ):
91
116
"""Collect a sample from stack frames."""
92
117
current_time = (time .time () * 1000 ) - self .start_time
@@ -120,83 +145,46 @@ def collect(self, stack_frames):
120
145
status_flags = thread_info .status
121
146
has_gil = bool (status_flags & THREAD_STATUS_HAS_GIL )
122
147
on_cpu = bool (status_flags & THREAD_STATUS_ON_CPU )
123
- unknown = bool (status_flags & THREAD_STATUS_UNKNOWN )
124
148
gil_requested = bool (status_flags & THREAD_STATUS_GIL_REQUESTED )
125
149
126
- # Track GIL possession (interval marker: "Has GIL" / "No GIL")
127
- if has_gil :
128
- if tid not in self .has_gil_start :
129
- self .has_gil_start [tid ] = current_time
130
- # End "No GIL" if it was running
131
- if tid in self .no_gil_start :
132
- start_time = self .no_gil_start [tid ]
133
- self ._add_marker (tid , "No GIL" , start_time , current_time , CATEGORY_GIL )
134
- del self .no_gil_start [tid ]
135
- else :
136
- if tid not in self .no_gil_start :
137
- self .no_gil_start [tid ] = current_time
138
- # End "Has GIL" if it was running
139
- if tid in self .has_gil_start :
140
- start_time = self .has_gil_start [tid ]
141
- self ._add_marker (tid , "Has GIL" , start_time , current_time , CATEGORY_GIL )
142
- del self .has_gil_start [tid ]
143
-
144
- # Track "On CPU" / "Off CPU" state
145
- if on_cpu :
146
- if tid not in self .on_cpu_start :
147
- self .on_cpu_start [tid ] = current_time
148
- # End "Off CPU" if it was running
149
- if tid in self .off_cpu_start :
150
- start_time = self .off_cpu_start [tid ]
151
- self ._add_marker (tid , "Off CPU" , start_time , current_time , CATEGORY_CPU )
152
- del self .off_cpu_start [tid ]
153
- else :
154
- if tid not in self .off_cpu_start :
155
- self .off_cpu_start [tid ] = current_time
156
- # End "On CPU" if it was running
157
- if tid in self .on_cpu_start :
158
- start_time = self .on_cpu_start [tid ]
159
- self ._add_marker (tid , "On CPU" , start_time , current_time , CATEGORY_CPU )
160
- del self .on_cpu_start [tid ]
161
-
162
- # Track "Python Code" (has GIL) / "Native Code" (on CPU without GIL)
150
+ # Track GIL possession (Has GIL / No GIL)
151
+ self ._track_state_transition (
152
+ tid , has_gil , self .has_gil_start , self .no_gil_start ,
153
+ "Has GIL" , "No GIL" , CATEGORY_GIL , current_time
154
+ )
155
+
156
+ # Track CPU state (On CPU / Off CPU)
157
+ self ._track_state_transition (
158
+ tid , on_cpu , self .on_cpu_start , self .off_cpu_start ,
159
+ "On CPU" , "Off CPU" , CATEGORY_CPU , current_time
160
+ )
161
+
162
+ # Track code type (Python Code / Native Code)
163
163
if has_gil :
164
- if tid not in self .python_code_start :
165
- self .python_code_start [tid ] = current_time
166
- # End "Native Code" if it was running
167
- if tid in self .native_code_start :
168
- start_time = self .native_code_start [tid ]
169
- self ._add_marker (tid , "Native Code" , start_time , current_time , CATEGORY_CODE_TYPE )
170
- del self .native_code_start [tid ]
164
+ self ._track_state_transition (
165
+ tid , True , self .python_code_start , self .native_code_start ,
166
+ "Python Code" , "Native Code" , CATEGORY_CODE_TYPE , current_time
167
+ )
171
168
elif on_cpu :
172
- # Native code: on CPU without GIL
173
- if tid not in self .native_code_start :
174
- self .native_code_start [tid ] = current_time
175
- # End "Python Code" if it was running
176
- if tid in self .python_code_start :
177
- start_time = self .python_code_start [tid ]
178
- self ._add_marker (tid , "Python Code" , start_time , current_time , CATEGORY_CODE_TYPE )
179
- del self .python_code_start [tid ]
169
+ self ._track_state_transition (
170
+ tid , True , self .native_code_start , self .python_code_start ,
171
+ "Native Code" , "Python Code" , CATEGORY_CODE_TYPE , current_time
172
+ )
180
173
else :
181
174
# Neither has GIL nor on CPU - end both if running
182
175
if tid in self .python_code_start :
183
- start_time = self .python_code_start [tid ]
184
- self ._add_marker (tid , "Python Code" , start_time , current_time , CATEGORY_CODE_TYPE )
185
- del self .python_code_start [tid ]
176
+ self ._add_marker (tid , "Python Code" , self .python_code_start .pop (tid ),
177
+ current_time , CATEGORY_CODE_TYPE )
186
178
if tid in self .native_code_start :
187
- start_time = self .native_code_start [tid ]
188
- self ._add_marker (tid , "Native Code" , start_time , current_time , CATEGORY_CODE_TYPE )
189
- del self .native_code_start [tid ]
179
+ self ._add_marker (tid , "Native Code" , self .native_code_start .pop (tid ),
180
+ current_time , CATEGORY_CODE_TYPE )
190
181
191
- # Track "Waiting for GIL" intervals
182
+ # Track "Waiting for GIL" intervals (one-sided tracking)
192
183
if gil_requested :
193
- if tid not in self .gil_wait_start :
194
- self .gil_wait_start [tid ] = current_time
195
- else :
196
- if tid in self .gil_wait_start :
197
- start_time = self .gil_wait_start [tid ]
198
- self ._add_marker (tid , "Waiting for GIL" , start_time , current_time , CATEGORY_GIL )
199
- del self .gil_wait_start [tid ]
184
+ self .gil_wait_start .setdefault (tid , current_time )
185
+ elif tid in self .gil_wait_start :
186
+ self ._add_marker (tid , "Waiting for GIL" , self .gil_wait_start .pop (tid ),
187
+ current_time , CATEGORY_GIL )
200
188
201
189
# Categorize: idle if neither has GIL nor on CPU
202
190
is_idle = not has_gil and not on_cpu
@@ -546,41 +534,21 @@ def _finalize_markers(self):
546
534
"""Close any open markers at the end of profiling."""
547
535
end_time = self .last_sample_time
548
536
549
- # Close all open markers for each thread
550
- for tid in list (self .has_gil_start .keys ()):
551
- start_time = self .has_gil_start [tid ]
552
- self ._add_marker (tid , "Has GIL" , start_time , end_time , CATEGORY_GIL )
553
- del self .has_gil_start [tid ]
554
-
555
- for tid in list (self .no_gil_start .keys ()):
556
- start_time = self .no_gil_start [tid ]
557
- self ._add_marker (tid , "No GIL" , start_time , end_time , CATEGORY_GIL )
558
- del self .no_gil_start [tid ]
559
-
560
- for tid in list (self .on_cpu_start .keys ()):
561
- start_time = self .on_cpu_start [tid ]
562
- self ._add_marker (tid , "On CPU" , start_time , end_time , CATEGORY_CPU )
563
- del self .on_cpu_start [tid ]
564
-
565
- for tid in list (self .off_cpu_start .keys ()):
566
- start_time = self .off_cpu_start [tid ]
567
- self ._add_marker (tid , "Off CPU" , start_time , end_time , CATEGORY_CPU )
568
- del self .off_cpu_start [tid ]
569
-
570
- for tid in list (self .python_code_start .keys ()):
571
- start_time = self .python_code_start [tid ]
572
- self ._add_marker (tid , "Python Code" , start_time , end_time , CATEGORY_CODE_TYPE )
573
- del self .python_code_start [tid ]
574
-
575
- for tid in list (self .native_code_start .keys ()):
576
- start_time = self .native_code_start [tid ]
577
- self ._add_marker (tid , "Native Code" , start_time , end_time , CATEGORY_CODE_TYPE )
578
- del self .native_code_start [tid ]
579
-
580
- for tid in list (self .gil_wait_start .keys ()):
581
- start_time = self .gil_wait_start [tid ]
582
- self ._add_marker (tid , "Waiting for GIL" , start_time , end_time , CATEGORY_GIL )
583
- del self .gil_wait_start [tid ]
537
+ # Close all open markers for each thread using a generic approach
538
+ marker_states = [
539
+ (self .has_gil_start , "Has GIL" , CATEGORY_GIL ),
540
+ (self .no_gil_start , "No GIL" , CATEGORY_GIL ),
541
+ (self .on_cpu_start , "On CPU" , CATEGORY_CPU ),
542
+ (self .off_cpu_start , "Off CPU" , CATEGORY_CPU ),
543
+ (self .python_code_start , "Python Code" , CATEGORY_CODE_TYPE ),
544
+ (self .native_code_start , "Native Code" , CATEGORY_CODE_TYPE ),
545
+ (self .gil_wait_start , "Waiting for GIL" , CATEGORY_GIL ),
546
+ ]
547
+
548
+ for state_dict , marker_name , category in marker_states :
549
+ for tid in list (state_dict .keys ()):
550
+ self ._add_marker (tid , marker_name , state_dict [tid ], end_time , category )
551
+ del state_dict [tid ]
584
552
585
553
# Close any open GC marker
586
554
if self .potential_gc_start is not None :
0 commit comments