Skip to content

Conversation

@michellewzhang
Copy link
Member

@michellewzhang michellewzhang commented Jul 19, 2024

current state of things:
we have distinct replayerStepper calls in the breadcrumbs + memory tabs. in total, we could be calling it 3 times because it's used by
(1) the breadcrumbs tab to render the HTML snippet,
(2) for hydration error text diffs,
(3) and the DOM nodes chart.

the plan:
improve things by calling replayerStepper once (or at least less) and memoize the results. side note: it was hard to combine the hydration error text diff replayerStepper call with the others due to the types, so we had to keep that call separate.

the question:
is it faster to run one replayerStepper instance, and collect all the data at one time (iterate over the frames exactly once)? this means the speed of any tab will always be limited by the slower call, since we're going through ALL the data at once, even some that we might not need yet. like this:

visitFrameCallbacks: {
extractDomNodes,
countDomNodes: countDomNodes(this.getRRWebMutations()),
},

or is it better to continue to have 2 stepper instances that iterate over 2 different sets of frames (breadcrumbs loops over hundreds of frames, and DOM node count iterates over thousands, which means the speeds could be drastically different). in this situation, each tab would handle their own array of necessary frames.

the experiments:
the first PR (#74540) tried to explore a refactor where replayerStepper is called once for the two DOM node actions (counting, for the memory chart, and extracting, for the breadcrumbs HTML). changes were made to the stepper so it could accept a list of callbacks, and return all types of data in one shot, so we only need one hidden replayer instance on the page for the DOM node functions.

unscientifically, it seemed like loading breadcrumbs + memory took the same amount of time as loading the memory chart. (see video below -- as soon as the breadcrumbs tab is done loading, the memory tab is already done).

Screen.Recording.2024-07-19.at.11.49.24.AM.mov

this makes sense because we’re iterating over thousands of frames for the DOM nodes chart, so that’s the bottleneck. this means the breadcrumbs tab loads slower.

this PR:
compared to that is the approach where we have 1 stepper instance for breadcrumbs, and one for DOM node count, each iterating over their own lists (which is what i'm doing in this PR). this approach showed breadcrumb data on the screen about 2x as fast as the approach above. therefore 2 stepper instances on the screen is better for users, especially since breadcrumbs tab is more popular than the memory tab.

the video below demonstrates the memoization in action. once the breadcrumbs or memory tabs are loaded, switching back to them does not cause re-loading, because the results are cached. notice that the breadcrumbs tab loading is not dependent on the loading of the DOM tab, unlike the video above where the breadcrumbs tab has to "wait" for the memory tab. (i'm also on slower wifi in this clip below)

Screen.Recording.2024-07-19.at.3.28.54.PM.mov

@michellewzhang michellewzhang requested a review from a team as a code owner July 19, 2024 22:30
@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jul 19, 2024
@michellewzhang michellewzhang requested a review from billyvg July 19, 2024 22:35
import DomNodesChart from 'sentry/views/replays/detail/memoryPanel/domNodesChart';
import MemoryChart from 'sentry/views/replays/detail/memoryPanel/memoryChart';

function useCountDomNodes({replay}: {replay: null | ReplayReader}) {
Copy link
Member Author

Choose a reason for hiding this comment

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

moved to its own file, useCountDomNodes.tsx

Copy link
Member Author

Choose a reason for hiding this comment

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

renamed to useExtractPageHtml.tsx

Copy link
Member Author

Choose a reason for hiding this comment

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

renamed to useExtractDomNodes.tsx

Comment on lines -33 to -57
export default function extractDomNodes({
frames,
rrwebEvents,
startTimestampMs,
}: Args): Promise<Map<ReplayFrame, Extraction>> {
return replayerStepper({
frames,
rrwebEvents,
startTimestampMs,
shouldVisitFrame: frame => {
const nodeId = getNodeId(frame);
return nodeId !== undefined && nodeId !== -1;
},
onVisitFrame: (frame, collection, replayer) => {
const mirror = replayer.getMirror();
const nodeId = getNodeId(frame);
const html = extractHtml(nodeId as number, mirror);
collection.set(frame as ReplayFrame, {
frame,
html,
timestamp: frame.timestampMs,
});
},
});
}
Copy link
Member Author

@michellewzhang michellewzhang Jul 19, 2024

Choose a reason for hiding this comment

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

moved to replayReader.tsx, so i renamed this file

Copy link
Member Author

@michellewzhang michellewzhang Jul 19, 2024

Choose a reason for hiding this comment

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

moved to replayReader.tsx, exported type was moved into the corresponding hook

Copy link
Member

@billyvg billyvg left a comment

Choose a reason for hiding this comment

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

What a great PR write-up!

@billyvg
Copy link
Member

billyvg commented Jul 23, 2024

It might be nice to have a loading indicator for the dom node chart

@michellewzhang
Copy link
Member Author

michellewzhang commented Jul 23, 2024

It might be nice to have a loading indicator for the dom node chart

@billyvg we had a placeholder before but seems our isFetching check no longer works. pushing a fix now! lmk if you have a strong preference for loading indicator vs placeholder

Screen.Recording.2024-07-23.at.12.12.23.PM.mov

Comment on lines 71 to 75
window.setTimeout(() => {
const timestamp =
'offsetMs' in frame ? frame.offsetMs : frame.timestamp - startTimestampMs;
replayer.pause(timestamp);
}, 0);
Copy link
Member

Choose a reason for hiding this comment

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

would RAF be better here @ryan953?

Copy link
Member

Choose a reason for hiding this comment

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

we could try it, and see if the memory chart renders the same as before.

also, we could experiment with different granularity in the memory chart.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

seems to be the same behavior after using RAF so i pushed up the change

@billyvg
Copy link
Member

billyvg commented Jul 23, 2024

It might be nice to have a loading indicator for the dom node chart

@billyvg we had a placeholder before but seems our isFetching check no longer works. pushing a fix now! lmk if you have a strong preference for loading indicator vs placeholder

Screen.Recording.2024-07-23.at.12.12.23.PM.mov

Placeholder looks great!

@michellewzhang michellewzhang merged commit 11e8424 into master Jul 23, 2024
@michellewzhang michellewzhang deleted the mz/replayer-ref-v2 branch July 23, 2024 20:39
@github-actions github-actions bot locked and limited conversation to collaborators Aug 8, 2024
@michellewzhang michellewzhang restored the mz/replayer-ref-v2 branch August 22, 2024 17:45
@michellewzhang michellewzhang deleted the mz/replayer-ref-v2 branch August 29, 2024 20:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants