-
Notifications
You must be signed in to change notification settings - Fork 558
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
Profiler RFC #51
Profiler RFC #51
Conversation
I certainly would use these timings / this component in my team's app. Identifying which "screen" suffers the most would be very valuable. Is there a model in this for profiling Also valuable: if it could automatically bubble up which sub-component is most at fault, or the number of re-renders of subcomponents. This way we can identify data misuse that leads to repeat renders. |
Would it be possible to detect/report on “useless” or unnecessary updates? |
Thank you both for your feedback 😄
I'm sorry, but I'm not sure I understand what you're suggesting. Could you elaborate?
I think this might be better handled by DevTools. (I have an idea that I hope to prototype for how the new Something like the number of re-renders seems easier to detect and handle locally, because it's easier to reproduce / more predictable (regardless of system specs). On the other hand, timing is often impacted by hardware capabilities and browser/versions, and may be difficult for devs to get an accurate idea about from their (generally high-end) machines. Also, timing is probably the most important "cost" of rendering. If a component re-renders unnecessarily, but it's super fast- is it actually that important? On the other hand, if it's slow each time the app renders- regardless of whether the render was "useless" or not, it should probably be looked into. That being said, I haven't thought a lot about non timing aspects of this. Maybe I'm overlooking something. Let's see what others think about it. 😄
Something like maicki/why-did-you-update? To be honest, I haven't considered this much. Initially, I think it might be tricky, because of e.g. inline functions for event handlers. (These might interact with the DOM even though it was unnecessary. Seems hard for React to detect without adding overhead of something like a per-renderer property whitelist, etc.) Definitely seems useful. Although similar to the number of renders/re-renders, it seems more predictable / easier to reproduce locally than timing- so maybe this is something that belongs in DEV mode only, rather than a profiling build? |
I think the notion of “useless” never made a lot of sense. It was a handy heuristic in some cases but it both failed to identify some “less useful” cases and incorrectly tagged “useful” updates as wasted. Now, I know a lot of people want to see this heuristic come back in some form. I think what we really want there is a scale rather than a yes/no flag. If a big tree was reconciled and it took a lot of time, but the result was just a couple of DOM changes, it’s still worth looking into, even though it’s not completely “wasted”. Similarly, a fast re-render of a few components that didn’t lead to DOM updates isn’t necessarily useful to act upon if it’s fast. So I think what we want is a scale of how much the update cost (in time) vs how much it affected the output (host effects, lifecycles). And we’d like to see the updates (as setState calls?) that have the worst ratio on that scale. I don’t know if it needs to be a part of this RFC. But that’s how I see “wasted” heuristic in the future if we add it again. Does that make sense? |
This looks really great!
Absolutely yes. We have a timing HOC at Twitter and it's not going to work once React hits v17.0.0 and deprecates some things. (it's rudimentary, but it gets us enough information to alert on critical regressions) As for this particular RFC:
Does this have to be unique? If I were to use the same The way it looks with the definition for |
@bvaughn in reply to your response: yes I totally agree that some of my requests might live better in the DevTools. -- I'm also considering this from the concept of a React-Native platform as my first target, I'm not sure that redirects your comments, but maybe that gives you more context? In regards to your question re my comment about "time-till-interactive" I'm trying to optimize for performance when many of the components are embedded in GraphQL Query components from Apollo. It's want to have more insight into how long it takes for my UI to "settle" which is a time-diff between componentDidMount and the last re-render after data comes back. I admit I'm a bit confused as to how to best profile & track which parts of my component tree need love. |
Thanks for sharing, I will definitely use it. I can think about the following ideas:
Again thanks for sharing, hope those 👆help 😁 |
Opinion: it would be amazing to have an option to toggle I think it might be useful to avoid using "hacks" (e.g. global variables, global functions) in production. E.g. you push potential bottlenecks wrapped into a disabled // EDIT Oh, basically I described 2nd idea from @alvaropinot. |
@paularmstrong Thanks!
Callbacks will always be called once per The ID is for your benefit only. If you are using a single, centralized logging function- the ID can help identify which
I'm not sure what you're saying. Clarify?
React Native support is a central focus for this new component, and will also be a central focus for the DevTools work I'll be doing soon. We realize that RN is currently a bit harder to profile than React DOM.
Seems like you should be able to infer this based on when your callback is called? The first time it is called for a tree, the
This is where React DevTools fits in! Can't wait to show you what Sebastian and I have been brainstorming here. 😄
You could do this via a HOC (if you wanted). The way we intend for this to work by default though will be: ON for development and profiling bundles, OFF for production. (It doesn't really matter if it's always on for dev mode, since the perf impact is minimal. You can switch which production bundle of React is used- regular or profiling- to turn it off for production mode.)
Yes! This is planned! 😁 Will share something soon. |
This is super exciting — especially that React Native support will be included from the start. I have a couple questions/suggestions:
<Timeout>
{didTimeout => didTimeout ? <Spinner /> : (
<Lots of other components on this screen>
<Profiler>
<AsyncResource />
</Profiler>
</Others>
)}
</Timeout> In this case, a cache miss on AsyncResource is going to cause everything under Timeout to unmount, including the Profiler. It'd still be interesting to be able to profile the cost of the mount/update phase that led to the immediate unmounting. (I'm not an expert on Suspense, so please forgive me if this use case is unclear. I'd be happy to clarify.) |
baseTime: number, | ||
startTime: number, | ||
commitTime: number | ||
): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not pass all of this as a single object so people can pick out the keys they need?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reasonable question!
I'd say the reasons are that I'm following precedent (we don't pass named parameters anywhere else that I can think of off the top of my head) and avoiding allocating a wrapper Object during commit.
I'd be interested to hear what others think about this aspect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote for an object, despite the fact that it breaks with existing React API precedent.
The order of these timing arguments is going to be tough to memorize, and I can imagine only being interested in a subset of them. Using an object also enables you to add additional timing data down the road.
I'd view it as somewhat analogous to an event object, which has a variety of keys, only some of which are of interest for any given listener.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would you need to memorize it? I imagine you'd only use Profiler in a few places in the app, and each time could consult the docs.
The need to avoid allocations is pretty important because adding GC pressure can skew the profiling results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if you use Profiler
in more than one place, the callback you pass it is likely shared- (this is why the id
parameter exists)- so you would only need to write these params (in the correct order) in a single place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, this concern makes sense. And I agree that we wouldn't be allocating too many new objects for this, because it would only be one per Profiler
per commit. I was just sharing rationale for why it is currently the way it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just my personal opinion here, but an object looks good from my point of view as long functions with more that 2/3 args are always hard to remember, you tend to start adding null
for the values that you might not want to use, I'm looking at you JSON.stringify(foo, null, 2)
😅 , you also need to remember the order and it's harder to refactor as you impact anyone already using that order.
Plus with the actual syntax for destructuring the function signature looks pretty much the same but with curly braces 😁, the best of two worlds!
onRenderCallback({ id, phase, actualTime, baseTime, startTime, commitTime })
vs
onRenderCallback(id, phase, actualTime, baseTime, startTime, commitTime)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that the Profiling API is out in the 16.4.1 release, I assume you decided to take no action on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unstable_Profiler
component was introduced in 16.4.0. The only thing that's new in 16.4.1 is a production+profiling build.
Unstable APIs can change. We haven't decided one way or another. This is kind of an open thread for discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just circling back on this particular thread. Sebastian and I chatted about this yesterday, and we've decided to avoid named parameters because the overhead of the wrapper objects (however small each individual one is) will add up in larger applications.
Correct.
The
In the scenario you describe, since the components underneath of the |
20: Update react monorepo to v16.4.0 r=renovate[bot] a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` - [react](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.0`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1640-May-23-2018) [Compare Source](facebook/react@8e5f12c...v16.4.0) ##### React * Add a new [experimental](`https://github.com/reactjs/rfcs/pull/51`) `React.unstable_Profiler` component for measuring performance. ([@​bvaughn] in [#​12745](`https://github.com/facebook/react/pull/12745`)) ##### React DOM * Add support for the Pointer Events specification. ([@​philipp-spiess] in [#​12507](`https://github.com/facebook/react/pull/12507`)) * Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@​acdlite] in [#​12600](`https://github.com/facebook/react/pull/12600`) and [#​12802](`https://github.com/facebook/react/pull/12802`)) * Fix a bug that prevented context propagation in some cases. ([@​gaearon] in [#​12708](`https://github.com/facebook/react/pull/12708`)) * Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@​gaearon] in [#​12690](`https://github.com/facebook/react/pull/12690`)) * Fix some attributes incorrectly getting removed from custom element nodes. ([@​airamrguez] in [#​12702](`https://github.com/facebook/react/pull/12702`)) * Fix context providers to not bail out on children if there's a legacy context provider above. ([@​gaearon] in [#​12586](`https://github.com/facebook/react/pull/12586`)) * Add the ability to specify `propTypes` on a context provider component. ([@​nicolevy] in [#​12658](`https://github.com/facebook/react/pull/12658`)) * Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@​sophiebits] in [#​12777](`https://github.com/facebook/react/pull/12777`)) * Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@​philipp-spiess] in [#​12629](`https://github.com/facebook/react/pull/12629`)) ##### React Test Renderer * Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@​koba04] in [#​12676](`https://github.com/facebook/react/pull/12676`)) * Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@​gaearon] in [#​12813](`https://github.com/facebook/react/pull/12813`)) * `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@​gaearon] in [#​12725](`https://github.com/facebook/react/pull/12725`)) * Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@​koba04] in [#​12756](`https://github.com/facebook/react/pull/12756`)) ##### React ART * Fix reading context provided from the tree managed by React DOM. ([@​acdlite] in [#​12779](`https://github.com/facebook/react/pull/12779`)) ##### React Call Return (Experimental) * This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@​gaearon] in [#​12820](`https://github.com/facebook/react/pull/12820`)) ##### React Reconciler (Experimental) * The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@​gaearon] in [#​12792](`https://github.com/facebook/react/pull/12792`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
164: Update react monorepo to v16.4.0 r=rehandalal a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` - [react](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.0`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1640-May-23-2018) [Compare Source](facebook/react@8e5f12c...v16.4.0) ##### React * Add a new [experimental](`https://github.com/reactjs/rfcs/pull/51`) `React.unstable_Profiler` component for measuring performance. ([@​bvaughn] in [#​12745](`https://github.com/facebook/react/pull/12745`)) ##### React DOM * Add support for the Pointer Events specification. ([@​philipp-spiess] in [#​12507](`https://github.com/facebook/react/pull/12507`)) * Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@​acdlite] in [#​12600](`https://github.com/facebook/react/pull/12600`) and [#​12802](`https://github.com/facebook/react/pull/12802`)) * Fix a bug that prevented context propagation in some cases. ([@​gaearon] in [#​12708](`https://github.com/facebook/react/pull/12708`)) * Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@​gaearon] in [#​12690](`https://github.com/facebook/react/pull/12690`)) * Fix some attributes incorrectly getting removed from custom element nodes. ([@​airamrguez] in [#​12702](`https://github.com/facebook/react/pull/12702`)) * Fix context providers to not bail out on children if there's a legacy context provider above. ([@​gaearon] in [#​12586](`https://github.com/facebook/react/pull/12586`)) * Add the ability to specify `propTypes` on a context provider component. ([@​nicolevy] in [#​12658](`https://github.com/facebook/react/pull/12658`)) * Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@​sophiebits] in [#​12777](`https://github.com/facebook/react/pull/12777`)) * Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@​philipp-spiess] in [#​12629](`https://github.com/facebook/react/pull/12629`)) ##### React Test Renderer * Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@​koba04] in [#​12676](`https://github.com/facebook/react/pull/12676`)) * Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@​gaearon] in [#​12813](`https://github.com/facebook/react/pull/12813`)) * `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@​gaearon] in [#​12725](`https://github.com/facebook/react/pull/12725`)) * Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@​koba04] in [#​12756](`https://github.com/facebook/react/pull/12756`)) ##### React ART * Fix reading context provided from the tree managed by React DOM. ([@​acdlite] in [#​12779](`https://github.com/facebook/react/pull/12779`)) ##### React Call Return (Experimental) * This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@​gaearon] in [#​12820](`https://github.com/facebook/react/pull/12820`)) ##### React Reconciler (Experimental) * The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@​gaearon] in [#​12792](`https://github.com/facebook/react/pull/12792`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
I don't understand what you mean with regard to this proposal. The profiler API does not use the |
@bvaughn, if that's the case, then great! I may be getting my tools mixed up here -- sorry for any misunderstanding. |
No worries 😄 |
Hi @bvaughn , a dumb question, will I be able to use the new Profile feature after I upgrade React to 16.5.0 and React Dev Tools to 3.3.2? |
Yes, the profiler plugin will work with 16.5– assuming you're either running in dev mode or using the profiling build (e.g. |
@lixiong-intelex, I also had to update react-dom to v16.5.0 to get the profiling tab to show. |
@jrnail23 that's what I found out as well. Thanks! |
Ah, I see the confusion. |
is this rfc settled? |
Probably. We'll likely close it in the next couple of weeks, when we remove the "unstable_" prefix. Why do you ask? |
was just browsing rfc's and saw this was still open thats all. thought it was stale. congrats on shipping! |
Can someone explain to me how wrap works with async calls? I'm trying to measure how long it takes to make api request and render: import React, { unstable_Profiler as Profiler } from "react";
import {
unstable_trace as trace,
unstable_wrap as wrap
} from "scheduler/tracing";
import axios from "axios";
import List from "./List";
class App extends React.Component {
state = {
count: 0,
data: null
};
componentDidMount() {
this.fetchData();
}
logProfile = (
id,
phase,
actualTime,
baseTime,
startTime,
commitTime,
interactions
) => {
console.log("--------- logProfile fired -----------");
console.log(`${id}'s ${phase.toUpperCase()} phase:`);
console.log(`Actual time: ${actualTime} ms`);
console.log(`Base time: ${baseTime} ms`);
console.log(`Start time (since component mounted): ${startTime} ms`);
console.log(`Commit time (since component mounted): ${commitTime} ms`);
console.log(interactions);
};
go = direction => () =>
this.setState(({ count }) => ({
count: direction === "up" ? count + 1 : count - 1
}));
fetchData = () => {
trace("Fetch todos", performance.now(), () => {
this.setState({ data: null });
wrap(async () => {
try {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/todos/"
);
this.setState({ data });
} catch (e) {
console.log(e);
}
})();
});
};
render() {
return (
<Profiler id="listPage" onRender={this.logProfile}>
<button onClick={this.go("up")}>up</button>
<div>The count is {this.state.count}</div>
<button onClick={this.go("down")}>down</button>
<hr />
<button onClick={() => this.fetchData()}>refetch</button>
<hr />
<List data={this.state.data} />
</Profiler>
);
}
}
export default App; |
``` | ||
|
||
#### `id: string` | ||
The `id` value of the `Profiler` tag that was measured. This value can change between renders if e.g. it is derived from `state` or `props`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused about that part. Why would the id
change between renders? It sounds like the second part was meant for an additional parameter e.g. something like reason
?
Was a bit confused about this part. It sounded like the id
might include some metadata why onRender
was called but what this refers to is the value of the id
prop e.g. <Profiler id={'tweets-' + props.userId} onRender={handleRenderCallBackWithChangingIds} />
@bvaughn What are the plans for this component regarding I wanted to experiment with the Profiler a bit and see if I can use this to create some performance regression test that work with a performance budget. While I'm not sure if this makes sense with The timings probably don't make much sense with the base or |
text/0000-profiler.md
Outdated
baseDuration: number, | ||
startTime: number, | ||
commitTime: number, | ||
interactions: Array<{ name: string, timestamp: number }>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shape changed slightly and matches in 16.8.4 (guess this originates in the scheduler
?)
interactions: Array<{ name: string, timestamp: number }>, | |
interactions: Set<{ id: number; name: string, timestamp: number }>, |
Will the implementation change or is the proposal outdated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah. This RFC is a bit outdated.
The test renderer is a reconciler-based renderer, so it could support profiling and the When Edit for clarity I don't think it makes sense to try to support this functionality in the shallow renderer. |
* feat(react): Add unstable_Profiler * OnRenderCallback -> ProfilerOnRenderCallback OnRenderCallback is to generic * Interaction -> SchedulerInteraction It's actually part of scheduler/tracing
FYI this RFC has entered the final comment period. I plan to merge it this week. |
View formatted RFC
Feedback is welcome!
Related PRs