https://github.com/python/cpython/commit/7908641e83ebcc21908a9a3fc699df239ddb5480
commit: 7908641e83ebcc21908a9a3fc699df239ddb5480
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-08-25T23:04:56Z
summary:

[3.15] gh-154088: Count elided stacks consistently (GH-154093) (#156386)

gh-154088: Count elided stacks consistently (GH-154093)
(cherry picked from commit b22d175c053b56fa534232715ff761a8cba9d636)

Co-authored-by: Pablo Galindo Salgado <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-19-11-30-00.gh-issue-154088.pC7nRa.rst
M Lib/profiling/sampling/stack_collector.py
M Lib/test/test_profiling/test_sampling_profiler/test_collectors.py

diff --git a/Lib/profiling/sampling/stack_collector.py 
b/Lib/profiling/sampling/stack_collector.py
index 796a900e084676f..190acf76fc152ca 100644
--- a/Lib/profiling/sampling/stack_collector.py
+++ b/Lib/profiling/sampling/stack_collector.py
@@ -212,7 +212,7 @@ def _get_module_name(self, filename, path_info):
             self._module_cache[filename] = module_name
         return module_name
 
-    def _convert_to_flamegraph_format(self):
+    def _convert_to_flamegraph_format(self, *, min_samples=None):
         if self._total_samples == 0:
             return {
                 "name": self._string_table.intern("No Data"),
@@ -302,7 +302,8 @@ def convert_children(children, min_samples, path_info):
 
         # Filter out very small functions (less than 0.1% of total samples)
         total_samples = self._total_samples
-        min_samples = max(1, int(total_samples * 0.001))
+        if min_samples is None:
+            min_samples = max(1, int(total_samples * 0.001))
         path_info = get_python_path_info()
 
         root_children = convert_children(self._root["children"], min_samples, 
path_info)
@@ -690,7 +691,15 @@ def _add_elided_flamegraph(self, current_flamegraph, 
current_stats, baseline_sta
         """Calculate elided paths and add elided flamegraph to stats."""
         self._elided_paths = baseline_stats.keys() - current_stats.keys()
 
-        current_flamegraph["stats"]["elided_count"] = len(self._elided_paths)
+        # A sampled stack can end at an internal path that also has elided
+        # descendants.  Count every disappeared path with self samples, not
+        # just the leaves of the elided path tree.
+        elided_stacks = {
+            path
+            for path in self._elided_paths
+            if baseline_stats[path]["self"] > 0
+        }
+        current_flamegraph["stats"]["elided_count"] = len(elided_stacks)
 
         if self._elided_paths:
             elided_flamegraph = self._build_elided_flamegraph(baseline_stats, 
scale)
@@ -713,7 +722,9 @@ def _build_elided_flamegraph(self, baseline_stats, scale):
         orig_get_source = self._baseline_collector._get_source_lines
         self._baseline_collector._get_source_lines = lambda func: None
         try:
-            baseline_data = 
self._baseline_collector._convert_to_flamegraph_format()
+            baseline_data = 
self._baseline_collector._convert_to_flamegraph_format(
+                min_samples=1
+            )
         finally:
             self._baseline_collector._get_source_lines = orig_get_source
 
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
index c27ad6663df1c8a..633c6a3dacb50ff 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
@@ -1665,7 +1665,7 @@ def test_diff_flamegraph_elided_stacks(self):
 
         data = diff._convert_to_flamegraph_format()
 
-        self.assertGreater(data["stats"]["elided_count"], 0)
+        self.assertEqual(data["stats"]["elided_count"], 1)
         self.assertIn("elided_flamegraph", data["stats"])
         elided = data["stats"]["elided_flamegraph"]
         self.assertTrue(elided["stats"]["is_differential"])
@@ -1681,6 +1681,74 @@ def test_diff_flamegraph_elided_stacks(self):
         self.assertGreater(child["baseline"], 0)
         self.assertAlmostEqual(child["diff"], -child["baseline"])
 
+    def test_diff_flamegraph_counts_elided_stacks_not_paths(self):
+        """Internal and leaf stack endings are counted separately."""
+        internal_stack = [
+            MockInterpreterInfo(0, [
+                MockThreadInfo(1, [
+                    MockFrameInfo("file.py", 20, "old_mid"),
+                    MockFrameInfo("file.py", 10, "root"),
+                ])
+            ])
+        ]
+        leaf_stack = [
+            MockInterpreterInfo(0, [
+                MockThreadInfo(1, [
+                    MockFrameInfo("file.py", 30, "old_leaf"),
+                    MockFrameInfo("file.py", 20, "old_mid"),
+                    MockFrameInfo("file.py", 10, "root"),
+                ])
+            ])
+        ]
+        current_frames = [
+            MockInterpreterInfo(0, [
+                MockThreadInfo(1, [MockFrameInfo("file.py", 10, "root")])
+            ])
+        ]
+
+        diff = make_diff_collector_with_mock_baseline(
+            [internal_stack, leaf_stack]
+        )
+        diff.collect(current_frames)
+
+        data = diff._convert_to_flamegraph_format()
+        self.assertEqual(data["stats"]["elided_count"], 2)
+
+    def test_diff_flamegraph_renders_small_elided_stack(self):
+        """Elided stacks are not removed by the significance filter."""
+        common_frames = [
+            MockInterpreterInfo(0, [
+                MockThreadInfo(1, [
+                    MockFrameInfo("file.py", 20, "common"),
+                    MockFrameInfo("file.py", 10, "root"),
+                ])
+            ])
+        ]
+        old_frames = [
+            MockInterpreterInfo(0, [
+                MockThreadInfo(1, [
+                    MockFrameInfo("file.py", 30, "old_tiny"),
+                    MockFrameInfo("file.py", 10, "root"),
+                ])
+            ])
+        ]
+
+        diff = make_diff_collector_with_mock_baseline(
+            [common_frames] * 1999 + [old_frames]
+        )
+        for _ in range(1999):
+            diff.collect(common_frames)
+
+        data = diff._convert_to_flamegraph_format()
+        self.assertEqual(data["stats"]["elided_count"], 1)
+        self.assertIn("elided_flamegraph", data["stats"])
+
+        elided = data["stats"]["elided_flamegraph"]
+        strings = elided["strings"]
+        self.assertIsNotNone(
+            find_child_by_name(elided.get("children", []), strings, "old_tiny")
+        )
+
     def test_diff_flamegraph_elided_top_level_root(self):
         """Elided top-level roots do not crash metadata generation."""
         baseline_frames_1 = [
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-19-11-30-00.gh-issue-154088.pC7nRa.rst 
b/Misc/NEWS.d/next/Library/2026-07-19-11-30-00.gh-issue-154088.pC7nRa.rst
new file mode 100644
index 000000000000000..b9747589a2f1c8e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-19-11-30-00.gh-issue-154088.pC7nRa.rst
@@ -0,0 +1,2 @@
+Make Tachyon count and render elided stacks consistently in differential
+flamegraphs.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to