diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index 166c03d03fbe5b..d7a8890d4a1ad9 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -292,6 +292,8 @@ function createPythonTooltip(data) { } const timeMs = (d.data.value / 1000).toFixed(2); + const selfSamples = d.data.self || 0; + const selfMs = (selfSamples / 1000).toFixed(2); const percentage = ((d.data.value / data.value) * 100).toFixed(2); const calls = d.data.calls || 0; const childCount = d.children ? d.children.length : 0; @@ -403,9 +405,14 @@ function createPythonTooltip(data) { ${fileLocationHTML}
- Execution Time: + Total Time: ${timeMs} ms + ${selfSamples > 0 ? ` + Self Time: + ${selfMs} ms + ` : ''} + Percentage: ${percentage}% @@ -1271,6 +1278,7 @@ function accumulateInvertedNode(parent, stackFrame, leaf, isDifferential) { const newNode = { name: stackFrame.name, value: 0, + self: 0, children: {}, filename: stackFrame.filename, lineno: stackFrame.lineno, @@ -1293,6 +1301,7 @@ function accumulateInvertedNode(parent, stackFrame, leaf, isDifferential) { const node = parent.children[key]; node.value += leaf.value; + node.self += stackFrame.self || 0; if (leaf.threads) { leaf.threads.forEach(t => node.threads.add(t)); } diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index 31102d3eb0ffa6..461ce95a25874b 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -207,6 +207,7 @@ def convert_children(children, min_samples): child_entry = { "name": name_idx, "value": samples, + "self": node.get("self", 0), "children": [], "filename": filename_idx, "lineno": func[1], 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 86fb9d4c05b3bc..503430ddf02163 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py @@ -435,12 +435,14 @@ def test_flamegraph_collector_basic(self): strings = data.get("strings", []) name = resolve_name(data, strings) self.assertTrue(name.startswith("Program Root: ")) - self.assertIn("func2 (file.py:20)", name) # formatted name + self.assertIn("func2 (file.py:20)", name) + self.assertEqual(data["self"], 0) # non-leaf: no self time children = data.get("children", []) self.assertEqual(len(children), 1) child = children[0] self.assertIn("func1 (file.py:10)", resolve_name(child, strings)) self.assertEqual(child["value"], 1) + self.assertEqual(child["self"], 1) # leaf: all time is self def test_flamegraph_collector_export(self): """Test flamegraph HTML export functionality."""