]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142927: Show self time in flamegraph tooltip (#147706)
authorivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com>
Sat, 4 Apr 2026 19:55:05 +0000 (20:55 +0100)
committerGitHub <noreply@github.com>
Sat, 4 Apr 2026 19:55:05 +0000 (20:55 +0100)
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.

Lib/profiling/sampling/_flamegraph_assets/flamegraph.js
Lib/profiling/sampling/stack_collector.py
Lib/test/test_profiling/test_sampling_profiler/test_collectors.py

index 166c03d03fbe5b5775a3548aa34e52b278b0474a..d7a8890d4a1ad95e21e95f2cc91223ee75996696 100644 (file)
@@ -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}
       </div>
       <div class="tooltip-stats">
-        <span class="tooltip-stat-label">Execution Time:</span>
+        <span class="tooltip-stat-label">Total Time:</span>
         <span class="tooltip-stat-value">${timeMs} ms</span>
 
+        ${selfSamples > 0 ? `
+          <span class="tooltip-stat-label">Self Time:</span>
+          <span class="tooltip-stat-value">${selfMs} ms</span>
+        ` : ''}
+
         <span class="tooltip-stat-label">Percentage:</span>
         <span class="tooltip-stat-value accent">${percentage}%</span>
 
@@ -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));
   }
index 31102d3eb0ffa6de780ee27628149ab1242cb64f..461ce95a25874ba535214085f3fa339cbdd62222 100644 (file)
@@ -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],
index 86fb9d4c05b3bc8a2bdc71da67ad970fddc0de97..503430ddf021630045f1a07144a0b2c165886958 100644 (file)
@@ -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."""