Skip to content

Commit

Permalink
feat(replay): Add experimental option to allow for a checkout every 6…
Browse files Browse the repository at this point in the history
… minutes (#13069)

Including more checkouts will improve replayer scrubbing since it will
reduce the number of mutations that need to be processed (especially for
longer replays). The downside is that it will increase the size of
replays since we will have up to 9 more snapshots per replay (max replay
duration is 60 minutes / 6 minute checkouts).
  • Loading branch information
billyvg authored Sep 10, 2024
1 parent 8fcee67 commit e944daa
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 6 deletions.
14 changes: 13 additions & 1 deletion packages/replay-internal/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,19 @@ export class ReplayContainer implements ReplayContainerInterface {
// When running in error sampling mode, we need to overwrite `checkoutEveryNms`
// Without this, it would record forever, until an error happens, which we don't want
// instead, we'll always keep the last 60 seconds of replay before an error happened
...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
...(this.recordingMode === 'buffer'
? { checkoutEveryNms: BUFFER_CHECKOUT_TIME }
: // Otherwise, use experimental option w/ min checkout time of 6 minutes
// This is to improve playback seeking as there could potentially be
// less mutations to process in the worse cases.
//
// checkout by "N" events is probably ideal, but means we have less
// control about the number of checkouts we make (which generally
// increases replay size)
this._options._experiments.continuousCheckout && {
// Minimum checkout time is 6 minutes
checkoutEveryNms: Math.max(360_000, this._options._experiments.continuousCheckout),
}),
emit: getHandleRecordingEmit(this),
onMutation: this._onMutationHandler,
...(canvasOptions
Expand Down
1 change: 1 addition & 0 deletions packages/replay-internal/src/types/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
_experiments: Partial<{
captureExceptions: boolean;
traceInternals: boolean;
continuousCheckout: number;
}>;
}

Expand Down
15 changes: 10 additions & 5 deletions packages/replay-internal/src/util/handleRecordingEmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
return false;
}

const session = replay.session;

// Additionally, create a meta event that will capture certain SDK settings.
// In order to handle buffer mode, this needs to either be done when we
// receive checkout events or at flush time.
// receive checkout events or at flush time. We have an experimental mode
// to perform multiple checkouts a session (the idea is to improve
// seeking during playback), so also only include if segmentId is 0
// (handled in `addSettingsEvent`).
//
// `isCheckout` is always true, but want to be explicit that it should
// only be added for checkouts
Expand All @@ -72,22 +77,22 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
// of the previous session. Do not immediately flush in this case
// to avoid capturing only the checkout and instead the replay will
// be captured if they perform any follow-up actions.
if (replay.session && replay.session.previousSessionId) {
if (session && session.previousSessionId) {
return true;
}

// When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
// this should usually be the timestamp of the checkout event, but to be safe...
if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
if (replay.recordingMode === 'buffer' && session && replay.eventBuffer) {
const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
if (earliestEvent) {
DEBUG_BUILD &&
logger.info(`Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`);

replay.session.started = earliestEvent;
session.started = earliestEvent;

if (replay.getOptions().stickySession) {
saveSession(replay.session);
saveSession(session);
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions packages/replay-internal/test/integration/rrweb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,44 @@ describe('Integration | rrweb', () => {
}
`);
});

it('calls rrweb.record with checkoutEveryNms', async () => {
const { mockRecord } = await resetSdkMock({
replayOptions: {
_experiments: {
continuousCheckout: 1,
},
},
sentryOptions: {
replaysOnErrorSampleRate: 0.0,
replaysSessionSampleRate: 1.0,
},
});

expect(mockRecord.mock.calls[0]?.[0]).toMatchInlineSnapshot(`
{
"blockSelector": ".sentry-block,[data-sentry-block],base[href="/"],img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]",
"checkoutEveryNms": 360000,
"collectFonts": true,
"emit": [Function],
"errorHandler": [Function],
"ignoreSelector": ".sentry-ignore,[data-sentry-ignore],input[type="file"]",
"inlineImages": false,
"inlineStylesheet": true,
"maskAllInputs": true,
"maskAllText": true,
"maskAttributeFn": [Function],
"maskInputFn": undefined,
"maskInputOptions": {
"password": true,
},
"maskTextFn": undefined,
"maskTextSelector": ".sentry-mask,[data-sentry-mask]",
"onMutation": [Function],
"slimDOMOptions": "all",
"unblockSelector": "",
"unmaskTextSelector": "",
}
`);
});
});

0 comments on commit e944daa

Please sign in to comment.