@@ -449,6 +449,8 @@ class DiffFlamegraphCollector(FlamegraphCollector):
449449
450450 def __init__ (self , sample_interval_usec , * , baseline_binary_path , skip_idle = False ):
451451 super ().__init__ (sample_interval_usec , skip_idle = skip_idle )
452+ if not os .path .exists (baseline_binary_path ):
453+ raise ValueError (f"Baseline file not found: { baseline_binary_path } " )
452454 self .baseline_binary_path = baseline_binary_path
453455 self ._baseline_collector = None
454456 self ._elided_paths = set ()
@@ -477,7 +479,8 @@ def _aggregate_path_samples(self, root_node, path=None):
477479 stats = {}
478480
479481 for func , node in root_node ["children" ].items ():
480- func_key = (func [0 ], func [2 ])
482+ filename , _lineno , funcname = func
483+ func_key = (filename , funcname )
481484 path_key = path + (func_key ,)
482485
483486 total_samples = node .get ("samples" , 0 )
@@ -508,15 +511,22 @@ def _convert_to_flamegraph_format(self):
508511 self ._load_baseline ()
509512
510513 current_flamegraph = super ()._convert_to_flamegraph_format ()
511- if self ._total_samples == 0 :
512- return current_flamegraph
513514
514515 current_stats = self ._aggregate_path_samples (self ._root )
515516 baseline_stats = self ._aggregate_path_samples (self ._baseline_collector ._root )
516517
517- # Scale baseline values to make them comparable when sample counts differ
518- scale = (self ._total_samples / self ._baseline_collector ._total_samples
519- if self ._baseline_collector ._total_samples > 0 else 1.0 )
518+ # Scale baseline values to make them comparable, accounting for both
519+ # sample count differences and sample interval differences.
520+ baseline_total = self ._baseline_collector ._total_samples
521+ if baseline_total > 0 and self ._total_samples > 0 :
522+ current_time = self ._total_samples * self .sample_interval_usec
523+ baseline_time = baseline_total * self ._baseline_collector .sample_interval_usec
524+ scale = current_time / baseline_time
525+ elif baseline_total > 0 :
526+ # Current profile is empty - use interval-based scale for elided display
527+ scale = self .sample_interval_usec / self ._baseline_collector .sample_interval_usec
528+ else :
529+ scale = 1.0
520530
521531 self ._annotate_nodes_with_diff (current_flamegraph , current_stats , baseline_stats , scale )
522532 self ._add_elided_flamegraph (current_flamegraph , current_stats , baseline_stats , scale )
@@ -575,7 +585,7 @@ def _is_promoted_root(self, data):
575585
576586 def _add_elided_flamegraph (self , current_flamegraph , current_stats , baseline_stats , scale ):
577587 """Calculate elided paths and add elided flamegraph to stats."""
578- self ._elided_paths = set ( baseline_stats .keys ()) - set ( current_stats .keys () )
588+ self ._elided_paths = baseline_stats .keys () - current_stats .keys ()
579589
580590 current_flamegraph ["stats" ]["elided_count" ] = len (self ._elided_paths )
581591
@@ -585,23 +595,39 @@ def _add_elided_flamegraph(self, current_flamegraph, current_stats, baseline_sta
585595 current_flamegraph ["stats" ]["elided_flamegraph" ] = elided_flamegraph
586596
587597 def _build_elided_flamegraph (self , baseline_stats , scale ):
588- """Build flamegraph containing only elided paths from baseline."""
598+ """Build flamegraph containing only elided paths from baseline.
599+
600+ This re-runs the base conversion pipeline on the baseline collector
601+ to produce a complete formatted flamegraph, then prunes it to keep
602+ only elided paths.
603+ """
589604 if not self ._baseline_collector or not self ._elided_paths :
590605 return None
591606
592- baseline_data = self ._baseline_collector ._convert_to_flamegraph_format ()
607+ # Suppress source line collection for elided nodes - these functions
608+ # no longer exist in the current profile, so source lines from the
609+ # current machine's filesystem would be misleading or unavailable.
610+ orig_get_source = self ._baseline_collector ._get_source_lines
611+ self ._baseline_collector ._get_source_lines = lambda func : None
612+ try :
613+ baseline_data = self ._baseline_collector ._convert_to_flamegraph_format ()
614+ finally :
615+ self ._baseline_collector ._get_source_lines = orig_get_source
593616
594617 # Remove non-elided nodes and recalculate values
595618 if not self ._extract_elided_nodes (baseline_data , path = ()):
596619 return None
597620
598621 self ._add_elided_metadata (baseline_data , baseline_stats , scale , path = ())
599622
600- baseline_data ["stats" ].update (self .stats )
623+ # Merge only profiling metadata, not thread-level stats
624+ for key in ("sample_interval_usec" , "duration_sec" , "sample_rate" ,
625+ "error_rate" , "missed_samples" , "mode" ):
626+ if key in self .stats :
627+ baseline_data ["stats" ][key ] = self .stats [key ]
601628 baseline_data ["stats" ]["is_differential" ] = True
602629 baseline_data ["stats" ]["baseline_samples" ] = self ._baseline_collector ._total_samples
603630 baseline_data ["stats" ]["current_samples" ] = self ._total_samples
604- baseline_data ["baseline_strings" ] = self ._baseline_collector ._string_table .get_strings ()
605631
606632 return baseline_data
607633
@@ -625,8 +651,9 @@ def _extract_elided_nodes(self, node, path):
625651 total_value += child .get ("value" , 0 )
626652 node ["children" ] = elided_children
627653
628- # Recalculate value based on remaining children
629- if elided_children :
654+ # Recalculate value for structural (non-elided) ancestor nodes;
655+ # elided nodes keep their original value to preserve self-samples
656+ if elided_children and not is_elided :
630657 node ["value" ] = total_value
631658
632659 # Keep this node if it's elided or has elided descendants
@@ -654,7 +681,14 @@ def _add_elided_metadata(self, node, baseline_stats, scale, path):
654681 node ["diff" ] = 0
655682
656683 node ["self_time" ] = 0
657- node ["diff_pct" ] = - 100.0
684+ # Elided paths have zero current self-time, so the change is always
685+ # -100% when there was actual baseline self-time to lose.
686+ # For internal nodes with no baseline self-time, use 0% to avoid
687+ # misleading tooltips.
688+ if baseline_self > 0 :
689+ node ["diff_pct" ] = - 100.0
690+ else :
691+ node ["diff_pct" ] = 0.0
658692
659693 if "children" in node and node ["children" ]:
660694 for child in node ["children" ]:
0 commit comments