diff --git a/OVERVIEW.md b/OVERVIEW.md index 72b23e7f83..9da3481961 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -8,7 +8,9 @@ One of the largest performance bottlenecks of the old React DevTools was the amo The old DevTools also rendered the entire application tree in the form of a large DOM structure of nested nodes. A secondary goal of the rewrite was to avoid rendering unnecessary nodes by using a windowing library (specifically [react-window](https://github.com/bvaughn/react-window)). -## Serializing the tree +## Elements panel + +### Serializing the tree Every React commit that changes the tree in a way DevTools cares about results in an "_operations_" message being sent across the bridge. These messages are lightweight patches that describe the changes that were made. (We don't resend the full tree structure like in legacy DevTools.) @@ -16,7 +18,7 @@ The payload for each message is a typed array. The first entry is a number ident We only send the following bits of information: element type, id, parent id, owner id, name, and key. Additional information (e.g. props, state) requires a separate "_inspectElement_" message. -### Adding a root node +#### Adding a root node Adding a root to the tree requires sending 3 numbers: @@ -33,7 +35,7 @@ For example, adding a root fiber with an id of 1: ] ``` -### Adding a leaf node +#### Adding a leaf node Adding a leaf node takes a variable number of numbers since we need to decode the name (and potentially the key): @@ -63,7 +65,7 @@ For example, adding a function component `` with an id 2: ] ``` -### Removing a node +#### Removing a node Removing a fiber from the tree (a root or a leaf) only requires sending 2 numbers: @@ -78,7 +80,7 @@ For example, removing a root fiber with an id of 1: ] ``` -### Re-ordering children +#### Re-ordering children 1. re-order children constant (`3`) 1. fiber id @@ -114,7 +116,7 @@ The frontend stores its information about the tree in a map of id to objects wit 2 The `weight` of an element is the number of elements (including itself) below it in the tree. We cache this property so that we can quickly determine the total number of Elements as well as to find the Nth element within that set. (This enables us to use windowing.) This value needs to be adjusted each time elements are added or removed from the tree, but we amortize this over time to avoid any big performance hits when rendering the tree. -### Finding the element at index N +#### Finding the element at index N The tree data structure lets us impose an order on elements and "quickly" find the Nth one using the `weight` attribute. @@ -160,3 +162,212 @@ while (index !== currentWeight) { } } ``` + +## Profiler + +All profiling information is stored on the backend while profiling is in progress. (Avoiding sending traffic across the bridge reduces the performance overhead of running the profiler.) + +### Profiling summary + +Upon completion, a "_profileSummary_" message is sent across the bridge. This message is a typed array summarizing the profiling session. It contains a repeating sequence of values (per root): + +1. root id +1. number of interactions for the root in this profiling session +1. number of commits for the root in this profiling session + +Followed by a series of tuples for each commit: + +1. timestamp (relative to when profiling was started) +1. duration of commit + +This is the minimal information required to render the main Profiler interface. + +For example, an application with two React roots might send a summary like this: +```js +[ + 1, // root id + 0, // no interactions were logged during this session + 3, // number of commits + 210, // first commit started 210ms after profiling began + 10, // and took 10ms + 284, // second commit started 284ms after profiling began + 13, // and took 13ms + 303, // third commit started 303ms after profiling began + 5, // and took 5ms + + 63, // root id + 1, // one interaction was logged during this session + 2, // number of commits + 513, // first commit started 513ms after profiling began + 8, // first commit took 8ms + 711, // second commit started 711ms after profiling began + 22, // second commit took 22ms +] +``` + +Additional information (e.g. which components were part of a specific commit, which interactions were logged) is lazily requested by the frontend as a user interacts with the profiling data. + +### Commit details + +When a particular commit is selected, the frontend polls the backend for the information necessary to display the ["flame chart"](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#flame-chart) and ["ranked chart"](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#ranked-chart) views. This information includes the time and duration of the commit, any interactions that were part of the commit, and a tree representing the state of the React application as of that commit. + +The frontend sends a "_profileCommitDetails_" message specifying which root and commit (index) it is interested in. The backend sends a response to fill in missing details about the commit. + +The response always beginning with 3 values: + +1. root id +1. commit index (which commit this describes) +1. number of interactions + +Next is a series of interactions (depending on the number specified previously) consisting of: + +1. interaction id +1. timestamp (when the interaction was traced relative to when profiling started) +1. UTF encoded interaction display name size + * (followed by this number of encoded values) + +Finally a flattened representation of the React tree as of this commit operation: + +1. element id +1. parent id +1. base duration +1. self duration +1. actual duration +1. UTF encoded display name size + * (followed by this number of encoded values) + +Here is an example commit containing two interactions and a tree of three React components: + +```js +[ + 1, // root id + 0, // commit index (the first commit) + 2, // the number of interactions (represented below) + + 1, // first interaction id + 4, // time when interaction was first traced + 3, // encoded interaction name size + 70, // "F" + 111, // "o" + 111, // "o" + + 1, // second interaction id + 5, // time when interaction was first traced + 3, // encoded interaction name size + 66, // "B" + 97, // "a" + 114, // "r" + + 1, // root fiber id + -1, // parent id (signifies the fiber is a root) + 15, // base duration + 4, // self duration + 15, // actual duration + 4, // UTF encoded display name size + 76, // "L" + 105, // "i" + 115, // "s" + 116, // "t" + + 2, // fiber id + 1, // parent id + 11, // base duration + 8, // self duration + 11, // actual duration + 4, // UTF encoded display name size + 73, // "I" + 116, // "t" + 101, // "e" + 109, // "m" + + 2, // fiber id + 1, // parent id + 8, // base duration + 0, // self duration + 0, // actual duration (this component didn't render during this commit) + 4, // UTF encoded display name size + 73, // "I" + 116, // "t" + 101, // "e" + 109, // "m" +] +``` + +### Component commits + +When a particular component (fiber) is selected, the frontend polls the backend for the aggregate data required to render the ["component chart"](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#component-chart) view. This information includes each time the component rendered and how long it took. + +The frontend sends a "_profileComponentDetails_" message specifying which root and commit number it is interested in. The backend sends a response that is serialized in a similar fashion as the Elements tree (above). + +The response consists of the following values: + +1. root id +1. fiber id +1. UTF encoded display name size + * (followed by this number of encoded values) + +Followed by a series of tuples for each time the fiber was committed. The tuples consist of: + +1. commit index +1. duration of time spent rendering the component in this commit + +Here is an example of a fiber that committed twice during a profiling session: + +```js +[ + 1, // root id + 2, // fiber id + 4, // UTF encoded display name size + 73, // "I" + 116, // "t" + 101, // "e" + 109, // "m" + + 0, // commit index 0 + 11, // actual duration for this fiber in commit 0 + + 3, // commit index 3 + 7, // actual duration for this fiber in commit 3 +] +``` + +### Interactions + +The [Interactions chart](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#interactions) shows a time series for every interaction that was traced in the recent profiler session. The frontend sends a "_profileInteractions_" message specifying which root it would like interaction data for. The backend sends a typed array as a response. + +The response always begins with an id that identifies which root the interactions are associated with: + +1. root id + +Next is a series of interactions, consisting of: + +1. interaction id +1. UTF encoded display name size + * (followed by this number of encoded values) +1. Number of commits this interaction was associated with + * (followed by the index of each commit) + +Here is an example of a profiling session consisting of two interactions: + +```js +[ + 1, // root id + + 1, // first interaction id + 3, // encoded interaction name size + 70, // "F" + 111, // "o" + 111, // "o" + 2, // number of commits this interaction was associated with + 0, // index of first commit + 3, // index of second commit + + 1, // second interaction id + 3, // encoded interaction name size + 66, // "B" + 97, // "a" + 114, // "r" + 1, // number of commits this interaction was associated with + 0, // index of first commit +] +``` \ No newline at end of file