@@ -56,50 +56,56 @@ def sample(self, collector, duration_sec=10):
5656 last_sample_time = start_time
5757 realtime_update_interval = 1.0 # Update every second
5858 last_realtime_update = start_time
59+ interrupted = False
5960
60- while running_time < duration_sec :
61- # Check if live collector wants to stop
62- if hasattr (collector , 'running' ) and not collector .running :
63- break
64-
65- current_time = time .perf_counter ()
66- if next_time < current_time :
67- try :
68- stack_frames = self .unwinder .get_stack_trace ()
69- collector .collect (stack_frames )
70- except ProcessLookupError :
71- duration_sec = current_time - start_time
61+ try :
62+ while running_time < duration_sec :
63+ # Check if live collector wants to stop
64+ if hasattr (collector , 'running' ) and not collector .running :
7265 break
73- except (RuntimeError , UnicodeDecodeError , MemoryError , OSError ):
74- collector .collect_failed_sample ()
75- errors += 1
76- except Exception as e :
77- if not self ._is_process_running ():
78- break
79- raise e from None
80-
81- # Track actual sampling intervals for real-time stats
82- if num_samples > 0 :
83- actual_interval = current_time - last_sample_time
84- self .sample_intervals .append (
85- 1.0 / actual_interval
86- ) # Convert to Hz
87- self .total_samples += 1
88-
89- # Print real-time statistics if enabled
90- if (
91- self .realtime_stats
92- and (current_time - last_realtime_update )
93- >= realtime_update_interval
94- ):
95- self ._print_realtime_stats ()
96- last_realtime_update = current_time
97-
98- last_sample_time = current_time
99- num_samples += 1
100- next_time += sample_interval_sec
10166
67+ current_time = time .perf_counter ()
68+ if next_time < current_time :
69+ try :
70+ stack_frames = self .unwinder .get_stack_trace ()
71+ collector .collect (stack_frames )
72+ except ProcessLookupError :
73+ duration_sec = current_time - start_time
74+ break
75+ except (RuntimeError , UnicodeDecodeError , MemoryError , OSError ):
76+ collector .collect_failed_sample ()
77+ errors += 1
78+ except Exception as e :
79+ if not self ._is_process_running ():
80+ break
81+ raise e from None
82+
83+ # Track actual sampling intervals for real-time stats
84+ if num_samples > 0 :
85+ actual_interval = current_time - last_sample_time
86+ self .sample_intervals .append (
87+ 1.0 / actual_interval
88+ ) # Convert to Hz
89+ self .total_samples += 1
90+
91+ # Print real-time statistics if enabled
92+ if (
93+ self .realtime_stats
94+ and (current_time - last_realtime_update )
95+ >= realtime_update_interval
96+ ):
97+ self ._print_realtime_stats ()
98+ last_realtime_update = current_time
99+
100+ last_sample_time = current_time
101+ num_samples += 1
102+ next_time += sample_interval_sec
103+
104+ running_time = time .perf_counter () - start_time
105+ except KeyboardInterrupt :
106+ interrupted = True
102107 running_time = time .perf_counter () - start_time
108+ print ("Interrupted by user." )
103109
104110 # Clear real-time stats line if it was being displayed
105111 if self .realtime_stats and len (self .sample_intervals ) > 0 :
@@ -120,7 +126,7 @@ def sample(self, collector, duration_sec=10):
120126 collector .set_stats (self .sample_interval_usec , running_time , sample_rate , error_rate )
121127
122128 expected_samples = int (duration_sec / sample_interval_sec )
123- if num_samples < expected_samples and not is_live_mode :
129+ if num_samples < expected_samples and not is_live_mode and not interrupted :
124130 print (
125131 f"Warning: missed { expected_samples - num_samples } samples "
126132 f"from the expected total of { expected_samples } "
0 commit comments