Description
Time sensitive API changes (ideally these should land before 18.0 release):
- Add DevTools API/hooks for collecting Timeline profiling data Refactored how React/DevTools log Timeline performance data #23102, Enable scheduling profiler flag in react-dom/testing builds #23142, Don't inject timeline hooks unless React supports profiling #23151
- React (DEV and profiling builds) should call the new Timeline hooks when present instead of logging User Timing data Refactored how React/DevTools log Timeline performance data #23102, Enable scheduling profiler flag in react-dom/testing builds #23142, Don't inject timeline hooks unless React supports profiling #23151
(DevTools will decide whether or not to store the data or log User Timing marks, since it knows when it's profiling.)
Additional, non-blocking changes:
DevTools hook changes:
- Log sync marks (with session ID) periodically (e.g. during commit) when profiling is active DevTools only records Timeline data when Profiling #23137
- Mark internal module ranges (see here) only if/when profiling is started. DevTools logs Timeline metadata only once (when profiling starts) #23141
-
Generate a unique session ID when profiling is started. Log it with User Timing and also store it with the main profiling data. (This will be used to match them up later.) - Collect component stacks for (maybe lazily? maybe cache per Fiber in a WeakMap?)
DevTools Timeline changes:
- Update pre-processing script to make use of the new React <-> DevTools data format DevTools: Profiler refactor incremental changes #23185
- Support optional tracing data DevTools: Profiler refactor incremental changes #23185
- Verify logic for aligning data shared between React <-> DevTools with the optional performance mark data DevTools: Profiler refactor incremental changes #23185
- Create UI for separate, optional import (and update the preprocessor to handle this data) DevTools: Profiler refactor incremental changes #23185
Future optimizations:
- Consider merging logic between
profilingHooks
andpreprocessData
to remove redundancies. If the code was moved somewhere else, thenprofilingHooks
could call it during rendering andpreprocessData
could call it too (as it processed each mark) and avoid having to build up the same in-memory representations. - Double check that
internalModuleSourceToRanges
is being initialized correctly for the in-memory profiler. (Technically this doesn't matter yet, since we don't have JavaScript samples for the in memory profiler– but still.)
Motivation
There are currently two React profilers: the "legacy" profiler (which reads data from Fibers during the commit phase) and the "scheduling" profiler (which reads data in the form of User Timing marks in a Chrome performance profile). This separation is confusing as both profilers live in the same extension/app but import/export different types of data.
Let's take a step back and revisit how the scheduling profiler works...
Recording a profile currently requires a user to do the following:
- Click "record" in Chrome
- Use the app
- Click "stop" in Chrome
- Export the profile JSON
- Import it into the React DevTools
It would be nice if DevTools could start/stop recording and import the data itself, but the only way to do this is using the Chrome DevTools Protocol which would require the use of an extremely powerful permission that I don't think we would want to ask users for.
This approach also has the downside of not working with React Native (or non-Chrome browsers).
So why do we do it? We do it because the profile gives us a lot of nice extra data: CPU samples of the JavaScript stack, Network requests, screenshots (if enabled), user events (e.g. "click").
But we don't need this data. The scheduling profiler could still be a useful tool even if included only the React specific marks.
My proposal then is that we consider doing this:
- Replace the user timing API with a direct React-to-DevTools API (where these marks are logged to DevTools directly).
- When profiling starts in React DevTools, generate a unique ID string
- Log this unique ID to the User Profiling API along with each commit (e.g.
"react-sync-marker-<uid>-<index>-<timestamp>"
) - Allow users to export/import additional native profiling information (which can be aligned using the sync marks) but is not required.
Doing this has a few benefits:
- We could start both profilers and join the commit and scheduling profiler data streams (huge benefit).
- It would make the profiler easier to use in the simple case (if you didn't want/need native profiler info) since you could start/stop it from within React DevTools.
- It would let us add component and call stacks without the extra serialization overhead.
It would have the following downside though:
- Recording profiles with native information would be a little more complicated, since you'd need to start React DevTools and then start the browser profiler. (We could show instructions for doing this in the DevTools profiler though.)
A note about component and call stacks
The scheduling profiler currently shows component names for things like state-updates (e.g. "state update scheduled by Foo") but it does not include component or call stacks. These would both be useful because:
- Component stack would help identify which instance of foo was e.g. scheduling the update
- Call stack would show e.g. which piece of state was being updated (and by what)
The reason we don't include this information is because we won't want to pay the cost of string serializing it all and logging it as a User Timing mark.