Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions Lib/profiling/sampling/stack_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,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"),
Expand Down Expand Up @@ -268,7 +268,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)
Expand Down Expand Up @@ -622,7 +623,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)
Expand All @@ -645,7 +654,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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,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"])
Expand All @@ -1597,6 +1597,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 = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make Tachyon count and render elided stacks consistently in differential
flamegraphs.
Loading