From: ivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com>
Date: Sat, 4 Apr 2026 19:55:05 +0000 (+0100)
Subject: gh-142927: Show self time in flamegraph tooltip (#147706)
X-Git-Tag: v3.15.0a8~33
X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4ff8b07a3d907c5a755a862a86496ed6c6fb2f3d;p=thirdparty%2FPython%2Fcpython.git
gh-142927: Show self time in flamegraph tooltip (#147706)
We already show self time in differential flamegraphs, but it should
be included in regular flamegraphs as well. Display the time spent
in the function body excluding callees, not just the total inclusive
time.
---
diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js
index 166c03d03fbe..d7a8890d4a1a 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 31102d3eb0ff..461ce95a2587 100644
--- a/Lib/profiling/sampling/stack_collector.py
+++ b/Lib/profiling/sampling/stack_collector.py
@@ -207,6 +207,7 @@ class FlamegraphCollector(StackTraceCollector):
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 86fb9d4c05b3..503430ddf021 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 @@ class TestSampleProfilerComponents(unittest.TestCase):
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."""