Skip to content

[Flight] Track the function name that was called for I/O entries #33392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 3, 2025

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33390.

The stack trace doesn't include the thing you called when calling into ignore listed content. We consider the ignore listed content conceptually the abstraction that you called that's interesting.

This extracts the name of the first ignore listed function that was called from user space. For example "fetch". So we can know what kind of request this is.

This could be enhanced and tweaked with heuristics in the future. For example, when you create a Promise yourself and call I/O inside of it like my delay examples, then we use that Promise as the I/O node but its stack doesn't have the actual I/O performed. It might be better to use the inner I/O node in that case. E.g. setTimeout. Currently I pick the name from the first party code instead - in my example delay.

Another case that could be improved is the case where your whole component is third-party. In that case we still log the I/O but it has no context about what kind of I/O since the whole stack is ignored it just gets the component name for example. We could for example look at the first name that is in a different package than the package name of the ignored listed component. So if node_modules/my-component-library/index.js calls into node_modules/mysql/connection.js then we could use the name from the inner.

@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label May 31, 2025
@react-sizebot
Copy link

react-sizebot commented May 31, 2025

Comparing: 3fb17d1...fed17d5

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 529.82 kB 529.82 kB = 93.51 kB 93.51 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 650.91 kB 650.91 kB = 114.63 kB 114.63 kB
facebook-www/ReactDOM-prod.classic.js = 675.86 kB 675.86 kB = 118.91 kB 118.91 kB
facebook-www/ReactDOM-prod.modern.js = 666.14 kB 666.14 kB = 117.30 kB 117.30 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.82% 162.93 kB 164.27 kB +1.01% 30.35 kB 30.66 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.82% 162.96 kB 164.30 kB +0.99% 30.36 kB 30.66 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.79% 169.63 kB 170.97 kB +0.93% 31.46 kB 31.75 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.79% 170.76 kB 172.11 kB +0.93% 31.75 kB 32.04 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.78% 170.83 kB 172.17 kB +0.92% 31.76 kB 32.05 kB

Generated by 🚫 dangerJS against fed17d5

if (ioNode.stack !== null) {
const fullStack = parseStackTrace(ioNode.stack, 1);
stack = filterStackTrace(request, fullStack);
name = findCalledFunctionNameFromStackTrace(request, fullStack);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should have a fallback here if we don't find a named stackframe. Otherwise createTask will fail since it requires a non-empty string.

I know we have a fallback introduced upstack but we can be a bit more specific. And this is the PR where this starts failing.

I'm trying to figure out why CI was fine with it.

Copy link
Collaborator Author

@sebmarkbage sebmarkbage Jun 3, 2025

Choose a reason for hiding this comment

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

We should be able to search for more things inside the I/O stack if we can't find it but there's some tradeoffs there, so it's complicated. It can be a follow up.

Regardless, we won't always find one or we may find <anonymous> which we also encode as empty. So the downstream needs to be resilient to this being empty.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I pushed a fix to handle the empty name case on the client. Went with just "unknown" for now.

name = findCalledFunctionNameFromStackTrace(request, fullStack);
// The name can include the object that this was called on but sometimes that's
// just unnecessary context.
if (name.startsWith('Window.')) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where is Window. coming from?

Copy link
Collaborator Author

@sebmarkbage sebmarkbage Jun 3, 2025

Choose a reason for hiding this comment

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

window.constructor.name. It mainly happens due to JS DOM but it can really happen when calling anything on window or even globally when one of the special cases to ignore it as a "method" call doesn't kick in.

Since like setTimeout(...) is actually window.setTimeout(...) which is a method call on an object of the Window class.

The fact that we need this cache is really a V8 quirk of only allowing structured data once.
It's technically possible that the implementation also doesn't need parsing and doesn't need a cache.

It's also not technically correct to use a shared cache for two different requests since the filter is on the request.

But mainly I'm doing this so that I can access the filtered frames.
filterStackTrace evolved into being both parsing and filtering. Split them
so we can use the same parsed value twice without relying on caching.
The name of the function that is actually called into from first party
code is not actually on the stack so we extract that separately.
@sebmarkbage sebmarkbage merged commit 65a46c7 into facebook:main Jun 3, 2025
240 checks passed
sebmarkbage added a commit that referenced this pull request Jun 3, 2025
Stacked on #33392.

This adds another track to the Performance Track called `"Server
Requests"`.

<img width="1015" alt="Screenshot 2025-06-01 at 12 02 14 AM"
src="https://github.com/user-attachments/assets/c4d164c4-cfdf-4e14-9a87-3f011f65fd20"
/>

This logs the flat list of I/O awaited on by Server Components. There
will be other views that are more focused on what data blocks a specific
Component or Suspense boundary but this is just the list of all the I/O
basically so you can get an overview of those waterfalls without the
noise of all the Component trees and rendering. It's similar to what the
"Network" track is on the client.

I've been going back and forth on what to call this track but I went
with `"Server Requests"` for now. The idea is that the name should
communicate that this is something that happens on the server and is a
pairing with the `"Server Components"` track. Although we don't use that
feature, since it's missing granularity, it's also similar to "Server
Timings".
Comment on lines +3611 to +3612
} else if (name.startsWith('<anonymous>.')) {
name = name.slice(7);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
} else if (name.startsWith('<anonymous>.')) {
name = name.slice(7);
} else if (name.startsWith('<anonymous>.')) {
name = name.slice(12);

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does GCC inline '<anonymous>.'.length? Ideally we'd just use that to spot mistakes more easily.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, it does.
Screenshot 2025-06-04 at 16 05 42

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants