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."""