@@ -210,6 +210,8 @@ def __init__(
210210 # Trend tracking (initialized after colors are set up)
211211 self ._trend_tracker = None
212212
213+ self ._seen_locations = set ()
214+
213215 @property
214216 def elapsed_time (self ):
215217 """Get the elapsed time, frozen when finished."""
@@ -305,15 +307,18 @@ def process_frames(self, frames, thread_id=None):
305307
306308 # Get per-thread data if tracking per-thread
307309 thread_data = self ._get_or_create_thread_data (thread_id ) if thread_id is not None else None
310+ self ._seen_locations .clear ()
308311
309312 # Process each frame in the stack to track cumulative calls
310313 # frame.location is (lineno, end_lineno, col_offset, end_col_offset), int, or None
311314 for frame in frames :
312315 lineno = extract_lineno (frame .location )
313316 location = (frame .filename , lineno , frame .funcname )
314- self .result [location ]["cumulative_calls" ] += 1
315- if thread_data :
316- thread_data .result [location ]["cumulative_calls" ] += 1
317+ if location not in self ._seen_locations :
318+ self ._seen_locations .add (location )
319+ self .result [location ]["cumulative_calls" ] += 1
320+ if thread_data :
321+ thread_data .result [location ]["cumulative_calls" ] += 1
317322
318323 # The top frame gets counted as an inline call (directly executing)
319324 top_frame = frames [0 ]
@@ -371,11 +376,13 @@ def collect(self, stack_frames):
371376 thread_data .gc_frame_samples += stats ["gc_samples" ]
372377
373378 # Process frames using pre-selected iterator
379+ frames_processed = False
374380 for frames , thread_id in self ._get_frame_iterator (stack_frames ):
375381 if not frames :
376382 continue
377383
378384 self .process_frames (frames , thread_id = thread_id )
385+ frames_processed = True
379386
380387 # Track thread IDs
381388 if thread_id is not None and thread_id not in self .thread_ids :
@@ -388,7 +395,11 @@ def collect(self, stack_frames):
388395 if has_gc_frame :
389396 self .gc_frame_samples += 1
390397
391- self .successful_samples += 1
398+ # Only count as successful if we actually processed frames
399+ # This is important for modes like --mode exception where most samples
400+ # may be filtered out at the C level
401+ if frames_processed :
402+ self .successful_samples += 1
392403 self .total_samples += 1
393404
394405 # Handle input on every sample for instant responsiveness
@@ -659,9 +670,11 @@ def build_stats_list(self):
659670 total_time = direct_calls * self .sample_interval_sec
660671 cumulative_time = cumulative_calls * self .sample_interval_sec
661672
662- # Calculate sample percentages
663- sample_pct = (direct_calls / self .total_samples * 100 ) if self .total_samples > 0 else 0
664- cumul_pct = (cumulative_calls / self .total_samples * 100 ) if self .total_samples > 0 else 0
673+ # Calculate sample percentages using successful_samples as denominator
674+ # This ensures percentages are relative to samples that actually had data,
675+ # not all sampling attempts (important for filtered modes like --mode exception)
676+ sample_pct = (direct_calls / self .successful_samples * 100 ) if self .successful_samples > 0 else 0
677+ cumul_pct = (cumulative_calls / self .successful_samples * 100 ) if self .successful_samples > 0 else 0
665678
666679 # Calculate trends for all columns using TrendTracker
667680 trends = {}
@@ -684,7 +697,9 @@ def build_stats_list(self):
684697 "cumulative_calls" : cumulative_calls ,
685698 "total_time" : total_time ,
686699 "cumulative_time" : cumulative_time ,
687- "trends" : trends , # Dictionary of trends for all columns
700+ "sample_pct" : sample_pct ,
701+ "cumul_pct" : cumul_pct ,
702+ "trends" : trends ,
688703 }
689704 )
690705
@@ -696,21 +711,9 @@ def build_stats_list(self):
696711 elif self .sort_by == "cumtime" :
697712 stats_list .sort (key = lambda x : x ["cumulative_time" ], reverse = True )
698713 elif self .sort_by == "sample_pct" :
699- stats_list .sort (
700- key = lambda x : (x ["direct_calls" ] / self .total_samples * 100 )
701- if self .total_samples > 0
702- else 0 ,
703- reverse = True ,
704- )
714+ stats_list .sort (key = lambda x : x ["sample_pct" ], reverse = True )
705715 elif self .sort_by == "cumul_pct" :
706- stats_list .sort (
707- key = lambda x : (
708- x ["cumulative_calls" ] / self .total_samples * 100
709- )
710- if self .total_samples > 0
711- else 0 ,
712- reverse = True ,
713- )
716+ stats_list .sort (key = lambda x : x ["cumul_pct" ], reverse = True )
714717
715718 return stats_list
716719
0 commit comments