Skip to content

High resource usage when using replay #16797

Open
@Keller18306

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;
    }
}),

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions