Skip to content

[Deploy preview] Add a function list - #5233

Draft
mstange wants to merge 20 commits into
firefox-devtools:mainfrom
mstange:function-list
Draft

mstange wants to merge 20 commits into
firefox-devtools:mainfrom
mstange:function-list

Conversation

@mstange

@mstange mstange commented Nov 28, 2024 •

Copy link
Copy Markdown
Contributor

Deploy preview

WIP implementing the approach outlined in #15 (comment)

@mstange
mstange force-pushed the function-list branch 4 times, most recently from 25bc285 to af75e01 Compare April 2, 2026 18:48
@mstange
mstange force-pushed the function-list branch 2 times, most recently from 26f8559 to abde12b Compare April 7, 2026 18:28
@codecov

codecov Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.40081% with 393 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.46%. Comparing base (7892c63) to head (46d1082).
⚠️ Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
src/components/calltree/SelfWing.tsx 1.85% 53 Missing ⚠️
src/components/calltree/WingTreeView.tsx 46.93% 52 Missing ⚠️
src/profile-logic/call-node-info.ts 88.03% 36 Missing ⚠️
src/profile-logic/flame-graph.ts 80.00% 35 Missing and 1 partial ⚠️
src/components/shared/WingContextMenu.tsx 79.60% 30 Missing and 1 partial ⚠️
src/components/calltree/LowerWingFlameGraph.tsx 3.33% 29 Missing ⚠️
src/app-logic/url-handling.ts 70.12% 23 Missing ⚠️
src/components/calltree/UpperWingFlameGraph.tsx 31.25% 22 Missing ⚠️
src/selectors/per-thread/stack-sample.ts 84.67% 21 Missing ⚠️
src/components/calltree/FunctionList.tsx 72.30% 18 Missing ⚠️
... and 12 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5233      +/-   ##
==========================================
- Coverage   84.78%   84.46%   -0.33%     
==========================================
  Files         358      367       +9     
  Lines       38946    40603    +1657     
  Branches    11019    11260     +241     
==========================================
+ Hits        33022    34294    +1272     
- Misses       5488     5869     +381     
- Partials      436      440       +4     

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

@mstange
mstange force-pushed the function-list branch 5 times, most recently from 86bf68a to 5059e16 Compare June 24, 2026 20:53
@mstange
mstange force-pushed the function-list branch 2 times, most recently from 50bbdd4 to 2667660 Compare July 3, 2026 19:08
@mstange
mstange force-pushed the function-list branch 2 times, most recently from 55c94a0 to 7b7cea8 Compare September 3, 2026 20:22
@mstange
mstange force-pushed the function-list branch 2 times, most recently from b76a337 to 253a2d1 Compare September 23, 2026 19:53
…ming.

The Canvas computed the tooltip percentage itself, by subtracting the box's
start from its end. That only works because the non-inverted flame graph
happens to lay boxes out relative to rootTotalSummary. Move the computation
behind a getRatioOfRootTotalSummary() method on FlameGraphTiming so that each
implementation can decide how its box widths relate to the percentage it wants
to display.
The flame graph currently derives box widths from rootTotalSummary, which also
serves as the 100% reference for the displayed percentages. The upcoming
function list wings need those to differ: a wing's root should fill the full
width, while percentages should stay relative to all filtered samples.

Add a separate flameGraphWidthTotal to CallTreeTimings for the width
reference. It equals rootTotalSummary everywhere so far, so nothing changes
yet.
Netlify reuses a cached checkout in which husky has set core.hooksPath.
Its 'preparing repo' stage runs git checkout before node is installed,
so the post-checkout hook failed with exit code 127 and aborted the
deploy preview.
Add a new "Function List" tab which shows a flat, sortable list of functions
rather than a tree. Selection is per-function instead of per-call-node, so
this adds a per-thread selected function to the URL state, along with the
URL handling, reducers and selectors that go with it.

Selecting a function replaces the current history entry instead of pushing a
new one. Otherwise, holding the down arrow key in the list would flood the
back/forward history and get rate-limited by the browser.
The call tree's context menu operates on a call node path, which the function
list does not have. Add a sibling menu that operates on a function index
instead. The menu items are described by a data table rather than repeated
JSX, because the same set of transforms is about to be reused by more than one
view.
These are the same single-key shortcuts the call tree context menu offers
(m, f, S, c, C, r, R, d), applied to the selected function.
When the function list tab is active, the activity graph should highlight
every sample whose stack contains the selected function, rather than the
samples belonging to a selected call node.
Clicking a sample already selects the corresponding self call node. When the
function list tab is active, also select that call node's function so the
click has an effect there too.
Turn CallNodeInfoInverted from a concrete class into an interface, and rename
the existing class to LazyInvertedCallNodeInfo. Along with that, stop assuming
that an inverted tree has exactly one root per function: the timings are now
keyed by root call node handle, obtained via getRootNodeForFunc(), rather than
by func index directly. For the lazy implementation the two are still the same
number, so this is behavior-preserving.

This makes room for an inverted CallNodeInfo whose roots are restricted to a
single function, which the next commit adds.
The lower wing answers "who calls the selected function?". It is an inverted
tree, but unlike the regular inverted tree it has a single root — the selected
function — and its totals count only the samples in which that function
appears.

LowerWingCallNodeInfo builds that tree eagerly from the selected function's
root-most entry points, expanding by inverted depth as the view asks for it.
computeLowerWingTimings maps each entry point's inclusive time onto the
corresponding node so that the percentages stay relative to the full thread.
The upper wing answers "what does the selected function call?". It is a
non-inverted tree rooted at the selected function, built by re-rooting the
thread's stacks at that function's entry points.
Lay out the lower wing as a flame graph. Because the lower wing tree is
materialized lazily by inverted depth, the layout is computed row by row and
memoizes its prefix sums rather than walking a fully built table.
…Inverted.

ThreadViewOptions stored the selected and expanded call node paths in four
separate fields, one pair for the non-inverted tree and one pair for the
inverted tree. Replace them with two records keyed by a CallNodeArea, and pass
the area in the CHANGE_SELECTED_CALL_NODE, CHANGE_EXPANDED_CALL_NODES and
CHANGE_RIGHT_CLICKED_CALL_NODE actions instead of an isInverted boolean.

This doesn't change behavior. It makes it possible to add more areas, each
with its own selected, expanded and right-clicked call node path.
The function list is about to grow "wings" around the selected function:
descendants (upper) and ancestors (lower). Each needs its own selected,
expanded and right-clicked call node path, so add LOWER_WING and UPPER_WING
call node areas next to the two tree areas.

The wings are rooted at the selected function. Mirror the selected function
into the wing paths whenever it changes, both from the function list and from
URL state, so that initial loads and back/forward navigation restore the wings
to the right function.

This commit adds the state, actions, reducers and call tree selectors; the
views that consume them follow.
A collapsible section with a header, used by the function list wings in the
next commit. The header can hold extra controls via headerActions.
Render the descendants and ancestors wings next to the function list, each in
its own disclosure box. Both are the same TreeView-based component, differing
only in which selectors they read and which context menu they open. Whether
each disclosure box is open is persisted in the URL.

The function list context menu is now shared between the function list and the
wings, so rename it to WingContextMenu and give it a second connected variant
for the lower wing, whose right-clicked target is a call node rather than a
bare function.
The upper and lower wings can now be displayed either as a call tree or as a
flame graph, and WingViewToggle in the disclosure box header switches between
them. The choice is per-wing and persisted in the URL.
The self wing shows where the selected function spends its own self time. It
is built from the focusSelf-filtered thread, taken before the implementation
filter so that native frames inside a JS function's self time stay visible
when the filter is set to "JS only".

Like the other wings, it has its own disclosure box and can be displayed as a
call tree or as a flame graph. The function list context menu now also checks
the self wing's call node table for recursion, because the focusSelf filter
can reveal recursion that the regular call node table hides.
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.

1 participant