From f7ae89ce07d5c4551117ce25d59eabdd166875e6 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 7 Jan 2025 13:29:13 -0500 Subject: [PATCH 1/2] feat(replay): Filter out `style` mutations when extracting DOM nodes We have an org that has a small handful of replays where the replayStepper causes massive perf issues to the extent that it freezes the browser. I narrowed it down to the `diff()` code inside of `rrdom` and a recent upstream PR (https://github.com/getsentry/rrweb/pull/233) seems to have exacerbated the problem. I have not been able to figure out the root cause for the perf issues, but it seems to be related to CSS and the mutations that add `style` elements. We will want to try to identify what exactly in these replays are causing the perf issues. In the meantime we can filter out these mutations. Since we are only interested in generating and extracting the HTML for certain breadcrumb events, the styles should have no affect on the data we are interested in using. Closes https://github.com/getsentry/sentry/issues/82221 --- .../replays/hooks/useExtractDomNodes.tsx | 2 +- static/app/utils/replays/replayReader.tsx | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/static/app/utils/replays/hooks/useExtractDomNodes.tsx b/static/app/utils/replays/hooks/useExtractDomNodes.tsx index 6a676364c119f4..557cdd0de398d5 100644 --- a/static/app/utils/replays/hooks/useExtractDomNodes.tsx +++ b/static/app/utils/replays/hooks/useExtractDomNodes.tsx @@ -10,7 +10,7 @@ export default function useExtractDomNodes({ }): UseQueryResult> { return useQuery({ queryKey: ['getDomNodes', replay], - queryFn: () => replay?.getExtractDomNodes(), + queryFn: () => replay?.getExtractDomNodes({withoutStyles: true}), enabled: Boolean(replay), gcTime: Infinity, }); diff --git a/static/app/utils/replays/replayReader.tsx b/static/app/utils/replays/replayReader.tsx index 835ba85cb40079..3d88d9fc7630d7 100644 --- a/static/app/utils/replays/replayReader.tsx +++ b/static/app/utils/replays/replayReader.tsx @@ -432,22 +432,26 @@ export default class ReplayReader { return this.processingErrors().length; }; - getExtractDomNodes = memoize(async () => { - if (this._fetching) { - return null; - } - const {onVisitFrame, shouldVisitFrame} = extractDomNodes; - - const results = await replayerStepper({ - frames: this.getDOMFrames(), - rrwebEvents: this.getRRWebFrames(), - startTimestampMs: this.getReplay().started_at.getTime() ?? 0, - onVisitFrame, - shouldVisitFrame, - }); + getExtractDomNodes = memoize( + async ({withoutStyles}: {withoutStyles?: boolean} = {}) => { + if (this._fetching) { + return null; + } + const {onVisitFrame, shouldVisitFrame} = extractDomNodes; + + const results = await replayerStepper({ + frames: this.getDOMFrames(), + rrwebEvents: withoutStyles + ? this.getRRWebFramesWithoutStyles() + : this.getRRWebFrames(), + startTimestampMs: this.getReplay().started_at.getTime() ?? 0, + onVisitFrame, + shouldVisitFrame, + }); - return results; - }); + return results; + } + ); getClipWindow = () => this._clipWindow; @@ -534,6 +538,35 @@ export default class ReplayReader { return eventsWithSnapshots; }); + /** + * Filter out style mutations as they can cause perf problems especially when + * used in replayStepper + */ + getRRWebFramesWithoutStyles = memoize(() => { + return this.getRRWebFrames().map(e => { + if ( + e.type === EventType.IncrementalSnapshot && + 'source' in e.data && + e.data.source === IncrementalSource.Mutation + ) { + return { + ...e, + data: { + ...e.data, + adds: e.data.adds.filter( + add => + !( + (add.node.type === 3 && add.node.isStyle) || + (add.node.type === 2 && add.node.tagName === 'style') + ) + ), + }, + }; + } + return e; + }); + }); + getRRwebTouchEvents = memoize(() => this.getRRWebFramesWithSnapshots().filter( e => isTouchEndFrame(e) || isTouchStartFrame(e) From da8232f057d52449d0004f1d01d82f67bfd8d4ae Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 7 Jan 2025 13:46:23 -0500 Subject: [PATCH 2/2] add some more comments --- static/app/utils/replays/hooks/useExtractDomNodes.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/app/utils/replays/hooks/useExtractDomNodes.tsx b/static/app/utils/replays/hooks/useExtractDomNodes.tsx index 557cdd0de398d5..961c174f5bd0aa 100644 --- a/static/app/utils/replays/hooks/useExtractDomNodes.tsx +++ b/static/app/utils/replays/hooks/useExtractDomNodes.tsx @@ -10,6 +10,9 @@ export default function useExtractDomNodes({ }): UseQueryResult> { return useQuery({ queryKey: ['getDomNodes', replay], + // Note: we filter out `style` mutations due to perf issues. + // We can do this as long as we only need the HTML and not need to + // visualize the rendered elements queryFn: () => replay?.getExtractDomNodes({withoutStyles: true}), enabled: Boolean(replay), gcTime: Infinity,