Skip to content

feat: invert call stack in flame graph to render as icicle graph#6090

Merged
mstange merged 5 commits into
firefox-devtools:mainfrom
cathaysia:feat/icicle_graph
Jul 7, 2026
Merged

feat: invert call stack in flame graph to render as icicle graph#6090
mstange merged 5 commits into
firefox-devtools:mainfrom
cathaysia:feat/icicle_graph

Conversation

@cathaysia

@cathaysia cathaysia commented Jun 5, 2026

Copy link
Copy Markdown
Contributor
image

add a invert callstack checkbox.

before:

image

after:
image

fix #6077

@cathaysia cathaysia force-pushed the feat/icicle_graph branch from 4f7ab4e to 2e38000 Compare June 5, 2026 02:46
@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.56522% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.50%. Comparing base (a542753) to head (b894055).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/selectors/per-thread/stack-sample.ts 80.76% 5 Missing ⚠️
src/profile-logic/flame-graph.ts 96.89% 4 Missing ⚠️
src/app-logic/url-handling.ts 83.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6090   +/-   ##
=======================================
  Coverage   83.50%   83.50%           
=======================================
  Files         342      342           
  Lines       36522    36522           
  Branches    10233    10221   -12     
=======================================
  Hits        30497    30497           
  Misses       5597     5597           
  Partials      428      428           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cathaysia

Copy link
Copy Markdown
Contributor Author

Hello, is anyone reviewing this PR? I don't have the permission to request a review.

@cathaysia

Copy link
Copy Markdown
Contributor Author

@mstange

mstange commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Unfortunately this implementation is too slow on large profiles. For example, clicking the "Invert call stack" checkbox on this flame graph causes the page to hang permanently:

https://deploy-preview-6090--perf-html.netlify.app/public/54908b38j3brzm4vw7mw3k3sqn3sf6vzfe95sxg/flame-graph/?globalTrackOrder=d59g1b7feac8x63402s6rx0mx4x3jthvilpnx5ux1qox2k&hiddenGlobalTracks=0wcewx6&profileName=Firefox%20Win%20Sp3%2015x%2020260304&thread=d&v=16

Furthermore, I think this view is only useful if most of the time is spent in a single leaf function. Once you have time spread over many different leaves, the inverted flame graph becomes less useful because all the root nodes will be very small boxes, and then their children are even smaller boxes.

However, what can be useful is the inverted flame graph for a single leaf function. In #5233 I'm prototyping a panel where you can select a function on the left side, and then see flame graphs for that function on the right side. When limited to a single function, computing the inverted flame graph can be done in reasonable time because the number of nodes in the tree is much more limited.

Here's an example:

https://deploy-preview-5233--perf-html.netlify.app/public/54908b38j3brzm4vw7mw3k3sqn3sf6vzfe95sxg/function-list/?funcListSections=descendants%2Cancestors&globalTrackOrder=d59g1b7feac8x63402s6rx0mx4x3jthvilpnx5ux1qox2k&hiddenGlobalTracks=0wcewx6&profileName=Firefox%20Win%20Sp3%2015x%2020260304&selectedFunc=23546&thread=d&v=17

@cathaysia

Copy link
Copy Markdown
Contributor Author

Thanks for the large-profile test case. I reproduced the hang locally with the profile from the preview link: computing the inverted flame graph timing for thread d ran out of memory with an 8GB JS heap.

I pushed 1f13270a2 to address this:

  • non-inverted flame graphs are back on the original FlameGraphRows + getFlameGraphTiming path, so they no longer go through the generic CallTree traversal;
  • inverted flame graphs now use a dedicated timing builder based on CallNodeInfoInverted + CallTreeTimingsInverted, avoiding CallTree.getNodeData() and the display-layer traversal;
  • the inverted builder prunes boxes narrower than 1 / 16384, because the flame graph has no horizontal zoom and those boxes cannot be meaningfully read or hovered even on very wide displays. This also avoids materializing huge invisible inverted subtrees.

With the same large profile / thread d, the selector now completes locally in about 1.6s and produces 242 rows / 42,238 boxes instead of OOMing.

I also added a regression test for the same-name-node case so the inverted tree does not combine unrelated call paths such as A -> B -> C and A -> C -> D into impossible paths.

@cathaysia

Copy link
Copy Markdown
Contributor Author

Actually, I think it would be more appropriate to move the data processing part to WASM. Rust can precisely control memory, and Rust itself provides many tools for benchmarking.

@cathaysia

Copy link
Copy Markdown
Contributor Author

🤖 This pull request will be closed in 15 days if there are no new non-automation comments.

tracked by #8

mstange added a commit that referenced this pull request Jul 3, 2026
This PR extracts some ideas from #6090. But it doesn't add UI to show an
inverted flame graph.

I'm planning to have an upside-down flame graph in the function list
(for the callers of the selected function), so the function list PR
depends on this one.
@mstange

mstange commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
* inverted flame graphs now use a dedicated timing builder based on `CallNodeInfoInverted + CallTreeTimingsInverted`, avoiding `CallTree.getNodeData()` and the display-layer traversal;

* the inverted builder prunes boxes narrower than `1 / 16384`, because the flame graph has no horizontal zoom and those boxes cannot be meaningfully read or hovered even on very wide displays. This also avoids materializing huge invisible inverted subtrees.

Hmm, interesting. But in the large example, this causes a large gap on the side (maybe 15% of the width) because the positions of the rendered boxes are no longer affected by the (narrow) width of the discarded boxes. It also makes for a rather sluggish experience when you drag a preview selection in the timeline while you're looking at the inverted flame graph, because not much of the flame graph information can be reused across different timings.

In the meantime I've landed #6126 and #6128. Can you try out another approach where you make a lazy FlameGraphRowsInverted and FlameGraphTimingsInverted and see if the optimization from #6126 to only render visible rows is good enough to achieve acceptable performance?

@cathaysia cathaysia force-pushed the feat/icicle_graph branch from 1f13270 to 095b392 Compare July 6, 2026 06:47
@cathaysia

Copy link
Copy Markdown
Contributor Author

Thanks for landing #6126 and #6128. I tested this again after rebasing locally onto current main, using the large profile from the earlier link (thread=d, local thread index 13).

A pure lazy FlameGraphTimingInverted / rows approach without tiny-node pruning was not sufficient for that profile. Building the inverted timing object was reasonable (~377ms), but materializing the first 40 visible rows still ran out of memory with an 8GB JS heap after around 110s. So the #6126 visible-row optimization helps avoid eagerly building the whole tree, but the visible shallow rows can still contain too much data.

I kept the lazy inverted timing shape, but kept the tiny-descendant pruning for boxes below 1 / 16384 of the width. The important change from the previous version is that skipped boxes still advance the horizontal position, so later boxes are not shifted left; this should avoid the large empty gap you observed.

With that version on the same profile, the inverted selector took ~523ms and materializing rows 0..39 took ~2.24s, producing 28,695 visible boxes without OOM. I also kept the upstream ConnectedFlameGraph / startsAtBottom structure from #6128.

@cathaysia cathaysia force-pushed the feat/icicle_graph branch from 095b392 to 9cd7d57 Compare July 6, 2026 06:51

@mstange mstange left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks pretty good! Just two minor changes. The performance sounds good enough for now. Maybe in the future we can improve the "drag a preview selection" case by caching more information in the flame graph rows (but only compute that information lazily when the flame graph timings ask for it).

Comment thread src/components/flame-graph/FlameGraph.tsx Outdated
Comment thread src/selectors/url-state.ts Outdated
@cathaysia cathaysia force-pushed the feat/icicle_graph branch 4 times, most recently from 8c8d3bb to 6c83b7d Compare July 7, 2026 02:41
Comment thread src/profile-logic/flame-graph.ts Outdated
@cathaysia cathaysia force-pushed the feat/icicle_graph branch from 6c83b7d to 64fe797 Compare July 7, 2026 06:05
cathaysia added 2 commits July 7, 2026 14:32
…lameGraph

As requested by the maintainer, this makes the invert state distinct for the call tree and flame graph panels so they default independently to non-inverted when switching tabs.
@cathaysia cathaysia force-pushed the feat/icicle_graph branch from 64fe797 to 5d397d9 Compare July 7, 2026 06:32

@mstange mstange left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

@mstange mstange enabled auto-merge July 7, 2026 14:27
@mstange mstange merged commit 4302ef2 into firefox-devtools:main Jul 7, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support icicle graph

2 participants