@@ -1664,7 +1664,8 @@ def test_diff_flamegraph_changed_functions(self):
16641664 ])
16651665 ]
16661666
1667- # Baseline: 2 samples, current: 4, scale = 2.0
1667+ # Baseline: 2 samples, current: 4. Profiles are compared in absolute
1668+ # time rather than normalized to the same total duration.
16681669 diff = make_diff_collector_with_mock_baseline (
16691670 [hot_leaf_sample , cold_leaf_sample ]
16701671 )
@@ -1674,25 +1675,25 @@ def test_diff_flamegraph_changed_functions(self):
16741675
16751676 data = diff ._convert_to_flamegraph_format ()
16761677 strings = data .get ("strings" , [])
1677- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 2 .0 )
1678+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
16781679
16791680 children = data .get ("children" , [])
16801681 hot_node = find_child_by_name (children , strings , "hot_leaf" )
16811682 cold_node = find_child_by_name (children , strings , "cold_leaf" )
16821683 self .assertIsNotNone (hot_node )
16831684 self .assertIsNotNone (cold_node )
16841685
1685- # hot_leaf regressed (+50 %)
1686- self .assertAlmostEqual (hot_node ["baseline" ], 2 .0 )
1686+ # hot_leaf regressed (+200 %)
1687+ self .assertAlmostEqual (hot_node ["baseline" ], 1 .0 )
16871688 self .assertEqual (hot_node ["self_time" ], 3 )
1688- self .assertAlmostEqual (hot_node ["diff" ], 1 .0 )
1689- self .assertAlmostEqual (hot_node ["diff_pct" ], 50 .0 )
1689+ self .assertAlmostEqual (hot_node ["diff" ], 2 .0 )
1690+ self .assertAlmostEqual (hot_node ["diff_pct" ], 200 .0 )
16901691
1691- # cold_leaf improved (-50%)
1692- self .assertAlmostEqual (cold_node ["baseline" ], 2 .0 )
1692+ # cold_leaf is unchanged
1693+ self .assertAlmostEqual (cold_node ["baseline" ], 1 .0 )
16931694 self .assertEqual (cold_node ["self_time" ], 1 )
1694- self .assertAlmostEqual (cold_node ["diff" ], - 1 .0 )
1695- self .assertAlmostEqual (cold_node ["diff_pct" ], - 50 .0 )
1695+ self .assertAlmostEqual (cold_node ["diff" ], 0 .0 )
1696+ self .assertAlmostEqual (cold_node ["diff_pct" ], 0 .0 )
16961697
16971698 def test_diff_flamegraph_rejects_mismatched_profiling_modes (self ):
16981699 from profiling .sampling .binary_collector import BinaryCollector
@@ -1738,8 +1739,8 @@ def test_diff_flamegraph_rejects_mismatched_capture_config(self):
17381739 with self .assertRaisesRegex (ValueError , "all_threads" ):
17391740 diff ._convert_to_flamegraph_format ()
17401741
1741- def test_diff_flamegraph_scale_factor (self ):
1742- """Scale factor adjusts when sample counts differ ."""
1742+ def test_diff_flamegraph_does_not_normalize_duration (self ):
1743+ """A longer current run is compared in absolute time ."""
17431744 baseline_frames = [
17441745 MockInterpreterInfo (0 , [
17451746 MockThreadInfo (1 , [
@@ -1754,15 +1755,74 @@ def test_diff_flamegraph_scale_factor(self):
17541755 diff .collect (baseline_frames )
17551756
17561757 data = diff ._convert_to_flamegraph_format ()
1757- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 4 .0 )
1758+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
17581759
17591760 children = data .get ("children" , [])
17601761 self .assertEqual (len (children ), 1 )
17611762 func1_node = children [0 ]
17621763 self .assertEqual (func1_node ["self_time" ], 4 )
1763- self .assertAlmostEqual (func1_node ["baseline" ], 4.0 )
1764- self .assertAlmostEqual (func1_node ["diff" ], 0.0 )
1765- self .assertAlmostEqual (func1_node ["diff_pct" ], 0.0 )
1764+ self .assertAlmostEqual (func1_node ["baseline" ], 1.0 )
1765+ self .assertAlmostEqual (func1_node ["diff" ], 3.0 )
1766+ self .assertAlmostEqual (func1_node ["diff_pct" ], 300.0 )
1767+
1768+ def test_diff_flamegraph_scale_factor_uses_sample_intervals (self ):
1769+ """Baseline samples are converted to current sample units."""
1770+ frames = [
1771+ MockInterpreterInfo (0 , [
1772+ MockThreadInfo (1 , [MockFrameInfo ("file.py" , 10 , "func1" )])
1773+ ])
1774+ ]
1775+
1776+ diff = make_diff_collector_with_mock_baseline (
1777+ [frames ] * 10 ,
1778+ baseline_interval = 1000 ,
1779+ current_interval = 10000 ,
1780+ )
1781+ diff .collect (frames )
1782+
1783+ data = diff ._convert_to_flamegraph_format ()
1784+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 0.1 )
1785+ self .assertAlmostEqual (data ["baseline" ], 1.0 )
1786+ self .assertEqual (data ["self_time" ], 1 )
1787+ self .assertAlmostEqual (data ["diff" ], 0.0 )
1788+ self .assertAlmostEqual (data ["diff_pct" ], 0.0 )
1789+
1790+ def test_diff_flamegraph_elided_values_use_current_interval (self ):
1791+ """Elided geometry and metadata use the same sample units."""
1792+ baseline_frames = [
1793+ MockInterpreterInfo (0 , [
1794+ MockThreadInfo (1 , [
1795+ MockFrameInfo ("file.py" , 10 , "old_func" ),
1796+ MockFrameInfo ("file.py" , 30 , "parent" ),
1797+ ])
1798+ ])
1799+ ]
1800+ current_frames = [
1801+ MockInterpreterInfo (0 , [
1802+ MockThreadInfo (1 , [
1803+ MockFrameInfo ("file.py" , 20 , "new_func" ),
1804+ MockFrameInfo ("file.py" , 30 , "parent" ),
1805+ ])
1806+ ])
1807+ ]
1808+
1809+ diff = make_diff_collector_with_mock_baseline (
1810+ [baseline_frames ] * 10 ,
1811+ baseline_interval = 1000 ,
1812+ current_interval = 10000 ,
1813+ )
1814+ diff .collect (current_frames )
1815+
1816+ data = diff ._convert_to_flamegraph_format ()
1817+ elided = data ["stats" ]["elided_flamegraph" ]
1818+ self .assertAlmostEqual (elided ["value" ], 1.0 )
1819+ self .assertEqual (elided ["self" ], 0 )
1820+ self .assertAlmostEqual (elided ["baseline_total" ], 1.0 )
1821+ child , = elided ["children" ]
1822+ self .assertAlmostEqual (child ["value" ], 1.0 )
1823+ self .assertAlmostEqual (child ["self" ], 1.0 )
1824+ self .assertAlmostEqual (child ["baseline" ], 1.0 )
1825+ self .assertAlmostEqual (child ["diff" ], - 1.0 )
17661826
17671827 def test_diff_flamegraph_elided_stacks (self ):
17681828 """Paths in baseline but not current produce elided stacks."""
@@ -2065,7 +2125,11 @@ def test_diff_flamegraph_empty_current(self):
20652125 ])
20662126 ]
20672127
2068- diff = make_diff_collector_with_mock_baseline ([baseline_frames ])
2128+ diff = make_diff_collector_with_mock_baseline (
2129+ [baseline_frames ] * 10 ,
2130+ baseline_interval = 1000 ,
2131+ current_interval = 10000 ,
2132+ )
20692133 # Don't collect anything in current
20702134
20712135 data = diff ._convert_to_flamegraph_format ()
@@ -2075,6 +2139,11 @@ def test_diff_flamegraph_empty_current(self):
20752139 self .assertTrue (data ["stats" ]["is_differential" ])
20762140 # All baseline paths should be elided since current is empty
20772141 self .assertGreater (data ["stats" ]["elided_count" ], 0 )
2142+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 0.1 )
2143+ elided = data ["stats" ]["elided_flamegraph" ]
2144+ self .assertAlmostEqual (elided ["value" ], 1.0 )
2145+ self .assertAlmostEqual (elided ["baseline" ], 1.0 )
2146+ self .assertAlmostEqual (elided ["diff" ], - 1.0 )
20782147
20792148 def test_diff_flamegraph_empty_baseline (self ):
20802149 """Empty baseline with non-empty current uses scale=1.0 fallback."""
@@ -2241,7 +2310,8 @@ def test_diff_flamegraph_load_baseline(self):
22412310 make_frame ("file.py" , 20 , "caller" ),
22422311 ])])]
22432312
2244- # Baseline: 2 samples, current: 4, scale = 2.0
2313+ # Baseline: 2 samples, current: 4. Profiles are compared in absolute
2314+ # time rather than normalized to the same total duration.
22452315 bin_file = tempfile .NamedTemporaryFile (suffix = ".bin" , delete = False )
22462316 self .addCleanup (close_and_unlink , bin_file )
22472317
@@ -2271,25 +2341,25 @@ def test_diff_flamegraph_load_baseline(self):
22712341 strings = data .get ("strings" , [])
22722342
22732343 self .assertTrue (data ["stats" ]["is_differential" ])
2274- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 2 .0 )
2344+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
22752345
22762346 children = data .get ("children" , [])
22772347 hot_node = find_child_by_name (children , strings , "hot_leaf" )
22782348 cold_node = find_child_by_name (children , strings , "cold_leaf" )
22792349 self .assertIsNotNone (hot_node )
22802350 self .assertIsNotNone (cold_node )
22812351
2282- # hot_leaf regressed (+50 %)
2283- self .assertAlmostEqual (hot_node ["baseline" ], 2 .0 )
2352+ # hot_leaf regressed (+200 %)
2353+ self .assertAlmostEqual (hot_node ["baseline" ], 1 .0 )
22842354 self .assertEqual (hot_node ["self_time" ], 3 )
2285- self .assertAlmostEqual (hot_node ["diff" ], 1 .0 )
2286- self .assertAlmostEqual (hot_node ["diff_pct" ], 50 .0 )
2355+ self .assertAlmostEqual (hot_node ["diff" ], 2 .0 )
2356+ self .assertAlmostEqual (hot_node ["diff_pct" ], 200 .0 )
22872357
2288- # cold_leaf improved (-50%)
2289- self .assertAlmostEqual (cold_node ["baseline" ], 2 .0 )
2358+ # cold_leaf is unchanged
2359+ self .assertAlmostEqual (cold_node ["baseline" ], 1 .0 )
22902360 self .assertEqual (cold_node ["self_time" ], 1 )
2291- self .assertAlmostEqual (cold_node ["diff" ], - 1 .0 )
2292- self .assertAlmostEqual (cold_node ["diff_pct" ], - 50 .0 )
2361+ self .assertAlmostEqual (cold_node ["diff" ], 0 .0 )
2362+ self .assertAlmostEqual (cold_node ["diff_pct" ], 0 .0 )
22932363
22942364 def test_jsonl_collector_export_exact_output (self ):
22952365 jsonl_out = tempfile .NamedTemporaryFile (delete = False )
0 commit comments