@@ -1663,7 +1663,8 @@ def test_diff_flamegraph_changed_functions(self):
16631663 ])
16641664 ]
16651665
1666- # Baseline: 2 samples, current: 4, scale = 2.0
1666+ # Baseline: 2 samples, current: 4. Profiles are compared in absolute
1667+ # time rather than normalized to the same total duration.
16671668 diff = make_diff_collector_with_mock_baseline (
16681669 [hot_leaf_sample , cold_leaf_sample ]
16691670 )
@@ -1673,25 +1674,25 @@ def test_diff_flamegraph_changed_functions(self):
16731674
16741675 data = diff ._convert_to_flamegraph_format ()
16751676 strings = data .get ("strings" , [])
1676- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 2 .0 )
1677+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
16771678
16781679 children = data .get ("children" , [])
16791680 hot_node = find_child_by_name (children , strings , "hot_leaf" )
16801681 cold_node = find_child_by_name (children , strings , "cold_leaf" )
16811682 self .assertIsNotNone (hot_node )
16821683 self .assertIsNotNone (cold_node )
16831684
1684- # hot_leaf regressed (+50 %)
1685- self .assertAlmostEqual (hot_node ["baseline" ], 2 .0 )
1685+ # hot_leaf regressed (+200 %)
1686+ self .assertAlmostEqual (hot_node ["baseline" ], 1 .0 )
16861687 self .assertEqual (hot_node ["self_time" ], 3 )
1687- self .assertAlmostEqual (hot_node ["diff" ], 1 .0 )
1688- self .assertAlmostEqual (hot_node ["diff_pct" ], 50 .0 )
1688+ self .assertAlmostEqual (hot_node ["diff" ], 2 .0 )
1689+ self .assertAlmostEqual (hot_node ["diff_pct" ], 200 .0 )
16891690
1690- # cold_leaf improved (-50%)
1691- self .assertAlmostEqual (cold_node ["baseline" ], 2 .0 )
1691+ # cold_leaf is unchanged
1692+ self .assertAlmostEqual (cold_node ["baseline" ], 1 .0 )
16921693 self .assertEqual (cold_node ["self_time" ], 1 )
1693- self .assertAlmostEqual (cold_node ["diff" ], - 1 .0 )
1694- self .assertAlmostEqual (cold_node ["diff_pct" ], - 50 .0 )
1694+ self .assertAlmostEqual (cold_node ["diff" ], 0 .0 )
1695+ self .assertAlmostEqual (cold_node ["diff_pct" ], 0 .0 )
16951696
16961697 def test_diff_flamegraph_rejects_mismatched_profiling_modes (self ):
16971698 from profiling .sampling .binary_collector import BinaryCollector
@@ -1737,8 +1738,8 @@ def test_diff_flamegraph_rejects_mismatched_capture_config(self):
17371738 with self .assertRaisesRegex (ValueError , "all_threads" ):
17381739 diff ._convert_to_flamegraph_format ()
17391740
1740- def test_diff_flamegraph_scale_factor (self ):
1741- """Scale factor adjusts when sample counts differ ."""
1741+ def test_diff_flamegraph_does_not_normalize_duration (self ):
1742+ """A longer current run is compared in absolute time ."""
17421743 baseline_frames = [
17431744 MockInterpreterInfo (0 , [
17441745 MockThreadInfo (1 , [
@@ -1753,15 +1754,74 @@ def test_diff_flamegraph_scale_factor(self):
17531754 diff .collect (baseline_frames )
17541755
17551756 data = diff ._convert_to_flamegraph_format ()
1756- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 4 .0 )
1757+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
17571758
17581759 children = data .get ("children" , [])
17591760 self .assertEqual (len (children ), 1 )
17601761 func1_node = children [0 ]
17611762 self .assertEqual (func1_node ["self_time" ], 4 )
1762- self .assertAlmostEqual (func1_node ["baseline" ], 4.0 )
1763- self .assertAlmostEqual (func1_node ["diff" ], 0.0 )
1764- self .assertAlmostEqual (func1_node ["diff_pct" ], 0.0 )
1763+ self .assertAlmostEqual (func1_node ["baseline" ], 1.0 )
1764+ self .assertAlmostEqual (func1_node ["diff" ], 3.0 )
1765+ self .assertAlmostEqual (func1_node ["diff_pct" ], 300.0 )
1766+
1767+ def test_diff_flamegraph_scale_factor_uses_sample_intervals (self ):
1768+ """Baseline samples are converted to current sample units."""
1769+ frames = [
1770+ MockInterpreterInfo (0 , [
1771+ MockThreadInfo (1 , [MockFrameInfo ("file.py" , 10 , "func1" )])
1772+ ])
1773+ ]
1774+
1775+ diff = make_diff_collector_with_mock_baseline (
1776+ [frames ] * 10 ,
1777+ baseline_interval = 1000 ,
1778+ current_interval = 10000 ,
1779+ )
1780+ diff .collect (frames )
1781+
1782+ data = diff ._convert_to_flamegraph_format ()
1783+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 0.1 )
1784+ self .assertAlmostEqual (data ["baseline" ], 1.0 )
1785+ self .assertEqual (data ["self_time" ], 1 )
1786+ self .assertAlmostEqual (data ["diff" ], 0.0 )
1787+ self .assertAlmostEqual (data ["diff_pct" ], 0.0 )
1788+
1789+ def test_diff_flamegraph_elided_values_use_current_interval (self ):
1790+ """Elided geometry and metadata use the same sample units."""
1791+ baseline_frames = [
1792+ MockInterpreterInfo (0 , [
1793+ MockThreadInfo (1 , [
1794+ MockFrameInfo ("file.py" , 10 , "old_func" ),
1795+ MockFrameInfo ("file.py" , 30 , "parent" ),
1796+ ])
1797+ ])
1798+ ]
1799+ current_frames = [
1800+ MockInterpreterInfo (0 , [
1801+ MockThreadInfo (1 , [
1802+ MockFrameInfo ("file.py" , 20 , "new_func" ),
1803+ MockFrameInfo ("file.py" , 30 , "parent" ),
1804+ ])
1805+ ])
1806+ ]
1807+
1808+ diff = make_diff_collector_with_mock_baseline (
1809+ [baseline_frames ] * 10 ,
1810+ baseline_interval = 1000 ,
1811+ current_interval = 10000 ,
1812+ )
1813+ diff .collect (current_frames )
1814+
1815+ data = diff ._convert_to_flamegraph_format ()
1816+ elided = data ["stats" ]["elided_flamegraph" ]
1817+ self .assertAlmostEqual (elided ["value" ], 1.0 )
1818+ self .assertEqual (elided ["self" ], 0 )
1819+ self .assertAlmostEqual (elided ["baseline_total" ], 1.0 )
1820+ child , = elided ["children" ]
1821+ self .assertAlmostEqual (child ["value" ], 1.0 )
1822+ self .assertAlmostEqual (child ["self" ], 1.0 )
1823+ self .assertAlmostEqual (child ["baseline" ], 1.0 )
1824+ self .assertAlmostEqual (child ["diff" ], - 1.0 )
17651825
17661826 def test_diff_flamegraph_elided_stacks (self ):
17671827 """Paths in baseline but not current produce elided stacks."""
@@ -2064,7 +2124,11 @@ def test_diff_flamegraph_empty_current(self):
20642124 ])
20652125 ]
20662126
2067- diff = make_diff_collector_with_mock_baseline ([baseline_frames ])
2127+ diff = make_diff_collector_with_mock_baseline (
2128+ [baseline_frames ] * 10 ,
2129+ baseline_interval = 1000 ,
2130+ current_interval = 10000 ,
2131+ )
20682132 # Don't collect anything in current
20692133
20702134 data = diff ._convert_to_flamegraph_format ()
@@ -2074,6 +2138,11 @@ def test_diff_flamegraph_empty_current(self):
20742138 self .assertTrue (data ["stats" ]["is_differential" ])
20752139 # All baseline paths should be elided since current is empty
20762140 self .assertGreater (data ["stats" ]["elided_count" ], 0 )
2141+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 0.1 )
2142+ elided = data ["stats" ]["elided_flamegraph" ]
2143+ self .assertAlmostEqual (elided ["value" ], 1.0 )
2144+ self .assertAlmostEqual (elided ["baseline" ], 1.0 )
2145+ self .assertAlmostEqual (elided ["diff" ], - 1.0 )
20772146
20782147 def test_diff_flamegraph_empty_baseline (self ):
20792148 """Empty baseline with non-empty current uses scale=1.0 fallback."""
@@ -2240,7 +2309,8 @@ def test_diff_flamegraph_load_baseline(self):
22402309 make_frame ("file.py" , 20 , "caller" ),
22412310 ])])]
22422311
2243- # Baseline: 2 samples, current: 4, scale = 2.0
2312+ # Baseline: 2 samples, current: 4. Profiles are compared in absolute
2313+ # time rather than normalized to the same total duration.
22442314 bin_file = tempfile .NamedTemporaryFile (suffix = ".bin" , delete = False )
22452315 self .addCleanup (close_and_unlink , bin_file )
22462316
@@ -2270,25 +2340,25 @@ def test_diff_flamegraph_load_baseline(self):
22702340 strings = data .get ("strings" , [])
22712341
22722342 self .assertTrue (data ["stats" ]["is_differential" ])
2273- self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 2 .0 )
2343+ self .assertAlmostEqual (data ["stats" ]["baseline_scale" ], 1 .0 )
22742344
22752345 children = data .get ("children" , [])
22762346 hot_node = find_child_by_name (children , strings , "hot_leaf" )
22772347 cold_node = find_child_by_name (children , strings , "cold_leaf" )
22782348 self .assertIsNotNone (hot_node )
22792349 self .assertIsNotNone (cold_node )
22802350
2281- # hot_leaf regressed (+50 %)
2282- self .assertAlmostEqual (hot_node ["baseline" ], 2 .0 )
2351+ # hot_leaf regressed (+200 %)
2352+ self .assertAlmostEqual (hot_node ["baseline" ], 1 .0 )
22832353 self .assertEqual (hot_node ["self_time" ], 3 )
2284- self .assertAlmostEqual (hot_node ["diff" ], 1 .0 )
2285- self .assertAlmostEqual (hot_node ["diff_pct" ], 50 .0 )
2354+ self .assertAlmostEqual (hot_node ["diff" ], 2 .0 )
2355+ self .assertAlmostEqual (hot_node ["diff_pct" ], 200 .0 )
22862356
2287- # cold_leaf improved (-50%)
2288- self .assertAlmostEqual (cold_node ["baseline" ], 2 .0 )
2357+ # cold_leaf is unchanged
2358+ self .assertAlmostEqual (cold_node ["baseline" ], 1 .0 )
22892359 self .assertEqual (cold_node ["self_time" ], 1 )
2290- self .assertAlmostEqual (cold_node ["diff" ], - 1 .0 )
2291- self .assertAlmostEqual (cold_node ["diff_pct" ], - 50 .0 )
2360+ self .assertAlmostEqual (cold_node ["diff" ], 0 .0 )
2361+ self .assertAlmostEqual (cold_node ["diff_pct" ], 0 .0 )
22922362
22932363 def test_jsonl_collector_export_exact_output (self ):
22942364 jsonl_out = tempfile .NamedTemporaryFile (delete = False )
0 commit comments