@@ -76,18 +76,18 @@ def _new_unwinder(self, native, gc, opcodes, skip_non_matching_threads):
7676 )
7777 return unwinder
7878
79- def sample (self , collector , duration_sec = 10 , * , async_aware = False ):
79+ def sample (self , collector , duration_sec = None , * , async_aware = False ):
8080 sample_interval_sec = self .sample_interval_usec / 1_000_000
81- running_time = 0
8281 num_samples = 0
8382 errors = 0
8483 interrupted = False
84+ running_time_sec = 0
8585 start_time = next_time = time .perf_counter ()
8686 last_sample_time = start_time
8787 realtime_update_interval = 1.0 # Update every second
8888 last_realtime_update = start_time
8989 try :
90- while running_time < duration_sec :
90+ while duration_sec is None or running_time_sec < duration_sec :
9191 # Check if live collector wants to stop
9292 if hasattr (collector , 'running' ) and not collector .running :
9393 break
@@ -104,7 +104,7 @@ def sample(self, collector, duration_sec=10, *, async_aware=False):
104104 stack_frames = self .unwinder .get_stack_trace ()
105105 collector .collect (stack_frames )
106106 except ProcessLookupError as e :
107- duration_sec = current_time - start_time
107+ running_time_sec = current_time - start_time
108108 break
109109 except (RuntimeError , UnicodeDecodeError , MemoryError , OSError ):
110110 collector .collect_failed_sample ()
@@ -135,25 +135,25 @@ def sample(self, collector, duration_sec=10, *, async_aware=False):
135135 num_samples += 1
136136 next_time += sample_interval_sec
137137
138- running_time = time .perf_counter () - start_time
138+ running_time_sec = time .perf_counter () - start_time
139139 except KeyboardInterrupt :
140140 interrupted = True
141- running_time = time .perf_counter () - start_time
141+ running_time_sec = time .perf_counter () - start_time
142142 print ("Interrupted by user." )
143143
144144 # Clear real-time stats line if it was being displayed
145145 if self .realtime_stats and len (self .sample_intervals ) > 0 :
146146 print () # Add newline after real-time stats
147147
148- sample_rate = num_samples / running_time if running_time > 0 else 0
148+ sample_rate = num_samples / running_time_sec if running_time_sec > 0 else 0
149149 error_rate = (errors / num_samples ) * 100 if num_samples > 0 else 0
150- expected_samples = int (duration_sec / sample_interval_sec )
150+ expected_samples = int (running_time_sec / sample_interval_sec )
151151 missed_samples = (expected_samples - num_samples ) / expected_samples * 100 if expected_samples > 0 else 0
152152
153153 # Don't print stats for live mode (curses is handling display)
154154 is_live_mode = LiveStatsCollector is not None and isinstance (collector , LiveStatsCollector )
155155 if not is_live_mode :
156- print (f"Captured { num_samples :n} samples in { fmt (running_time , 2 )} seconds" )
156+ print (f"Captured { num_samples :n} samples in { fmt (running_time_sec , 2 )} seconds" )
157157 print (f"Sample rate: { fmt (sample_rate , 2 )} samples/sec" )
158158 print (f"Error rate: { fmt (error_rate , 2 )} " )
159159
@@ -166,7 +166,7 @@ def sample(self, collector, duration_sec=10, *, async_aware=False):
166166
167167 # Pass stats to flamegraph collector if it's the right type
168168 if hasattr (collector , 'set_stats' ):
169- collector .set_stats (self .sample_interval_usec , running_time , sample_rate , error_rate , missed_samples , mode = self .mode )
169+ collector .set_stats (self .sample_interval_usec , running_time_sec , sample_rate , error_rate , missed_samples , mode = self .mode )
170170
171171 if num_samples < expected_samples and not is_live_mode and not interrupted :
172172 print (
@@ -363,7 +363,7 @@ def sample(
363363 pid ,
364364 collector ,
365365 * ,
366- duration_sec = 10 ,
366+ duration_sec = None ,
367367 all_threads = False ,
368368 realtime_stats = False ,
369369 mode = PROFILING_MODE_WALL ,
@@ -378,7 +378,8 @@ def sample(
378378 Args:
379379 pid: Process ID to sample
380380 collector: Collector instance to use for gathering samples
381- duration_sec: How long to sample for (seconds)
381+ duration_sec: How long to sample for (seconds), or None to run until
382+ the process exits or interrupted
382383 all_threads: Whether to sample all threads
383384 realtime_stats: Whether to print real-time sampling statistics
384385 mode: Profiling mode - WALL (all samples), CPU (only when on CPU),
@@ -427,7 +428,7 @@ def sample_live(
427428 pid ,
428429 collector ,
429430 * ,
430- duration_sec = 10 ,
431+ duration_sec = None ,
431432 all_threads = False ,
432433 realtime_stats = False ,
433434 mode = PROFILING_MODE_WALL ,
0 commit comments