Skip to content

High resource usage when using replay #16797

Description

@Keller18306

Problem Statement

I use react-draggable for my project. This package allows you to drag and drop elements on the page. However, when I installed Sentry, I encountered very high resource consumption. After debugging Sentry, I realized that MutaionObserver catches all style attribute changes and then sends them to Service Worker. There are so many events that the processor core is clogged up to 100%. In addition, the movement of elements becomes ragged and intermittent.

Solution Brainstorm

Image

While browsing in the debugger I noticed an interesting function options.onMutation(mutations). In the file node_modules/@sentry/react/node_modules/node_modules/@sentry-internal/rrweb/dist/rrweb.js (I couldn't find the source code, so I attached it as is)

function initMutationObserver(options, rootEl) {
  const mutationBuffer = new MutationBuffer();
  mutationBuffers.push(mutationBuffer);
  mutationBuffer.init(options);
  let mutationObserverCtor = window.MutationObserver || /**
  * Some websites may disable MutationObserver by removing it from the window object.
  * If someone is using rrweb to build a browser extention or things like it, they
  * could not change the website's code but can have an opportunity to inject some
  * code before the website executing its JS logic.
  * Then they can do this to store the native MutationObserver:
  * window.__rrMutationObserver = MutationObserver
  */
  window.__rrMutationObserver;
  const angularZoneSymbol = window?.Zone?.__symbol__?.("MutationObserver");
  if (angularZoneSymbol && window[angularZoneSymbol]) {
    mutationObserverCtor = window[angularZoneSymbol];
  }
  const observer = new mutationObserverCtor(
    callbackWrapper((mutations) => {
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      if (options.onMutation && options.onMutation(mutations) === false) {
        return;
      }
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      mutationBuffer.processMutations.bind(mutationBuffer)(mutations);
    })
  );
  observer.observe(rootEl, {
    attributes: true,
    attributeOldValue: true,
    characterData: true,
    characterDataOldValue: true,
    childList: true,
    subtree: true
  });
  return observer;
}

It calls the code provided below.

private _onMutationHandler(mutations: unknown[]): boolean {
const count = mutations.length;
const mutationLimit = this._options.mutationLimit;
const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
const overMutationLimit = mutationLimit && count > mutationLimit;
// Create a breadcrumb if a lot of mutations happen at the same time
// We can show this in the UI as an information with potential performance improvements
if (count > mutationBreadcrumbLimit || overMutationLimit) {
const breadcrumb = createBreadcrumb({
category: 'replay.mutations',
data: {
count,
limit: overMutationLimit,
},
});
this._createCustomBreadcrumb(breadcrumb);
}
// Stop replay if over the mutation limit
if (overMutationLimit) {
// This should never reject
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.stop({ reason: 'mutationLimit', forceFlush: this.recordingMode === 'session' });
return false;
}
// `true` means we use the regular mutation handling by rrweb
return true;
}

Why not make a call, for example, another call to a callback function, which will be specified in options to Sentry.replayIntegration. Where it will be possible to filter out those events that the user considers unnecessary?

Sentry.replayIntegration({
    beforeMutationEvent(events: MutationRecord[]): boolean {
         // my condition, to exclude some style change events for specified element
         return true;
    }
}),

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions