From 328402a0d9b87b66d1cd8db55f16ad803b0f604f Mon Sep 17 00:00:00 2001 From: JonasBa Date: Wed, 14 Jun 2023 13:41:08 -0400 Subject: [PATCH 01/11] feat: bring in hook related logic --- .../browser/src/profiling/hubextensions.ts | 180 ++++++++++++------ packages/browser/src/profiling/integration.ts | 134 +++++++++++-- .../browser/src/profiling/jsSelfProfiling.ts | 10 +- packages/browser/src/profiling/sendProfile.ts | 89 --------- packages/browser/src/profiling/utils.ts | 175 ++++++++++++++++- .../dedupePerformanceEntries.benchmark.ts | 47 +++++ .../src/util/dedupePerformanceEntries.new.ts | 102 ++++++++++ 7 files changed, 560 insertions(+), 177 deletions(-) delete mode 100644 packages/browser/src/profiling/sendProfile.ts create mode 100644 packages/replay/src/util/dedupePerformanceEntries.benchmark.ts create mode 100644 packages/replay/src/util/dedupePerformanceEntries.new.ts diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index e2d94a11d33f..c1cd8d8a1165 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -1,7 +1,8 @@ -import { getCurrentHub, getMainCarrier } from '@sentry/core'; -import type { CustomSamplingContext, Hub, Transaction, TransactionContext } from '@sentry/types'; +import { getCurrentHub } from '@sentry/core'; +import type { CustomSamplingContext, Transaction } from '@sentry/types'; import { logger, uuid4 } from '@sentry/utils'; +import type { BrowserClient } from '../client'; import { WINDOW } from '../helpers'; import type { JSSelfProfile, @@ -9,20 +10,126 @@ import type { JSSelfProfilerConstructor, ProcessedJSSelfProfile, } from './jsSelfProfiling'; -import { sendProfile } from './sendProfile'; +import { isValidSampleRate } from './utils'; -// Max profile duration. -const MAX_PROFILE_DURATION_MS = 30_000; +export const MAX_PROFILE_DURATION_MS = 30_000; // Keep a flag value to avoid re-initializing the profiler constructor. If it fails // once, it will always fail and this allows us to early return. let PROFILING_CONSTRUCTOR_FAILED = false; -// While we experiment, per transaction sampling interval will be more flexible to work with. -type StartTransaction = ( - this: Hub, - transactionContext: TransactionContext, +// Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the +// profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well +/** + * + */ +export function maybeProfileTransaction( + client: BrowserClient, + transaction: Transaction, customSamplingContext?: CustomSamplingContext, -) => Transaction | undefined; +): string | undefined { + // profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform + // the actual multiplication to get the final rate, but we discard the profile if the transaction was sampled, + // so anything after this block from here is based on the transaction sampling. + if (!transaction.sampled) { + return; + } + + // Client and options are required for profiling + if (!client) { + __DEBUG_BUILD__ && logger.log('[Profiling] Profiling disabled, no client found.'); + return; + } + + const options = client.getOptions(); + if (!options) { + __DEBUG_BUILD__ && logger.log('[Profiling] Profiling disabled, no options found.'); + return; + } + + // @ts-ignore profilesSampler is not part of the browser options yet + const profilesSampler = options.profilesSampler; + // @ts-ignore profilesSampleRate is not part of the browser options yet + let profilesSampleRate: number | boolean | undefined = options.profilesSampleRate; + + // Prefer sampler to sample rate if both are provided. + if (typeof profilesSampler === 'function') { + profilesSampleRate = profilesSampler({ transactionContext: transaction.toContext(), ...customSamplingContext }); + } + + // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The + // only valid values are booleans or numbers between 0 and 1.) + if (!isValidSampleRate(profilesSampleRate)) { + __DEBUG_BUILD__ && logger.warn('[Profiling] Discarding profile because of invalid sample rate.'); + return; + } + + // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped + if (!profilesSampleRate) { + __DEBUG_BUILD__ && + logger.log( + `[Profiling] Discarding profile because ${ + typeof profilesSampler === 'function' + ? 'profileSampler returned 0 or false' + : 'a negative sampling decision was inherited or profileSampleRate is set to 0' + }`, + ); + return; + } + + // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is + // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false. + const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate; + // Check if we should sample this profile + if (!sampled) { + __DEBUG_BUILD__ && + logger.log( + `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number( + profilesSampleRate, + )})`, + ); + return; + } + + const profile_id = uuid4(); + CpuProfilerBindings.startProfiling(profile_id); + + __DEBUG_BUILD__ && logger.log(`[Profiling] started profiling transaction: ${transaction.name}`); + + // set transaction context - do this regardless if profiling fails down the line + // so that we can still see the profile_id in the transaction context + return profile_id; +} + +/** + * + */ +export function stopTransactionProfile( + transaction: Transaction, + profile_id: string | undefined, +): ReturnType<(typeof CpuProfilerBindings)['stopProfiling']> | null { + // Should not happen, but satisfy the type checker and be safe regardless. + if (!profile_id) { + return null; + } + + const profile = CpuProfilerBindings.stopProfiling(profile_id); + + __DEBUG_BUILD__ && logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name}`); + + // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile. + if (!profile) { + __DEBUG_BUILD__ && + logger.log( + `[Profiling] profiler returned null profile for: ${transaction.name}`, + 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started', + ); + return null; + } + + // Assign profile_id to the profile + profile.profile_id = profile_id; + return profile; +} /** * Check if profiler constructor is available. @@ -247,56 +354,3 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { transaction.finish = profilingWrappedTransactionFinish; return transaction; } - -/** - * Wraps startTransaction with profiling logic. This is done automatically by the profiling integration. - */ -function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction: StartTransaction): StartTransaction { - return function wrappedStartTransaction( - this: Hub, - transactionContext: TransactionContext, - customSamplingContext?: CustomSamplingContext, - ): Transaction | undefined { - const transaction: Transaction | undefined = startTransaction.call(this, transactionContext, customSamplingContext); - if (transaction === undefined) { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Transaction is undefined, skipping profiling'); - } - return transaction; - } - - return wrapTransactionWithProfiling(transaction); - }; -} - -/** - * Patches startTransaction and stopTransaction with profiling logic. - */ -export function addProfilingExtensionMethods(): void { - const carrier = getMainCarrier(); - if (!carrier.__SENTRY__) { - if (__DEBUG_BUILD__) { - logger.log("[Profiling] Can't find main carrier, profiling won't work."); - } - return; - } - carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {}; - - if (!carrier.__SENTRY__.extensions['startTransaction']) { - if (__DEBUG_BUILD__) { - logger.log( - '[Profiling] startTransaction does not exists, profiling will not work. Make sure you import @sentry/tracing package before @sentry/profiling-node as import order matters.', - ); - } - return; - } - - if (__DEBUG_BUILD__) { - logger.log('[Profiling] startTransaction exists, patching it with profiling functionality...'); - } - - carrier.__SENTRY__.extensions['startTransaction'] = __PRIVATE__wrapStartTransactionWithProfiling( - // This is already patched by sentry/tracing, we are going to re-patch it... - carrier.__SENTRY__.extensions['startTransaction'] as StartTransaction, - ); -} diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index 9a9751c50d61..cd79fc56916d 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -1,8 +1,23 @@ -import type { Event, EventProcessor, Integration } from '@sentry/types'; +import type { BrowserClient } from '@sentry/browser'; +import type { Event, EventProcessor, Hub, Integration, Transaction } from '@sentry/types'; import { logger } from '@sentry/utils'; import { PROFILING_EVENT_CACHE } from './cache'; -import { addProfilingExtensionMethods } from './hubextensions'; +import { MAX_PROFILE_DURATION_MS, maybeProfileTransaction } from './hubextensions'; +import { addProfilesToEnvelope, findProfiledTransactionsFromEnvelope } from './utils'; + +const MAX_PROFILE_QUEUE_LENGTH = 50; +const PROFILE_QUEUE: RawThreadCpuProfile[] = []; +const PROFILE_TIMEOUTS: Record = {}; + +function addToProfileQueue(profile: RawThreadCpuProfile): void { + PROFILE_QUEUE.push(profile); + + // We only want to keep the last n profiles in the queue. + if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_LENGTH) { + PROFILE_QUEUE.shift(); + } +} /** * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] @@ -15,19 +30,116 @@ import { addProfilingExtensionMethods } from './hubextensions'; */ export class BrowserProfilingIntegration implements Integration { public readonly name: string = 'BrowserProfilingIntegration'; + public getCurrentHub?: () => Hub = undefined; /** * @inheritDoc */ - public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void { - // Patching the hub to add the extension methods. - // Warning: we have an implicit dependency on import order and we will fail patching if the constructor of - // BrowserProfilingIntegration is called before @sentry/tracing is imported. This is because we need to patch - // the methods of @sentry/tracing which are patched as a side effect of importing @sentry/tracing. - addProfilingExtensionMethods(); - - // Add our event processor - addGlobalEventProcessor(this.handleGlobalEvent.bind(this)); + public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + this.getCurrentHub = getCurrentHub; + const client = this.getCurrentHub().getClient() as BrowserClient; + + if (client && typeof client.on === 'function') { + client.on('startTransaction', (transaction: Transaction) => { + const profile_id = maybeProfileTransaction(client, transaction, undefined); + + if (profile_id) { + const options = client.getOptions(); + // Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that + // currently exceed the default timeout set by the SDKs. + const maxProfileDurationMs = + (options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS; + + // Enqueue a timeout to prevent profiles from running over max duration. + if (PROFILE_TIMEOUTS[profile_id]) { + global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); + delete PROFILE_TIMEOUTS[profile_id]; + } + + PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => { + __DEBUG_BUILD__ && + logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', transaction.name); + + const profile = stopTransactionProfile(transaction, profile_id); + if (profile) { + addToProfileQueue(profile); + } + }, maxProfileDurationMs); + + transaction.setContext('profile', { profile_id }); + // @ts-expect-error profile_id is not part of the metadata type + transaction.setMetadata({ profile_id: profile_id }); + } + }); + + client.on('finishTransaction', transaction => { + // @ts-expect-error profile_id is not part of the metadata type + const profile_id = transaction && transaction.metadata && transaction.metadata.profile_id; + if (profile_id) { + if (PROFILE_TIMEOUTS[profile_id]) { + global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); + delete PROFILE_TIMEOUTS[profile_id]; + } + const profile = stopTransactionProfile(transaction, profile_id); + + if (profile) { + addToProfileQueue(profile); + } + } + }); + + client.on('beforeEnvelope', (envelope): void => { + // if not profiles are in queue, there is nothing to add to the envelope. + if (!PROFILE_QUEUE.length) { + return; + } + + const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); + if (!profiledTransactionEvents.length) { + return; + } + + const profilesToAddToEnvelope: Profile[] = []; + + for (const profiledTransaction of profiledTransactionEvents) { + const profile_id = profiledTransaction?.contexts?.['profile']?.['profile_id']; + + if (!profile_id) { + throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); + } + + // Remove the profile from the transaction context before sending, relay will take care of the rest. + if (profiledTransaction?.contexts?.['.profile']) { + delete profiledTransaction.contexts.profile; + } + + // We need to find both a profile and a transaction event for the same profile_id. + const profileIndex = PROFILE_QUEUE.findIndex(p => p.profile_id === profile_id); + if (profileIndex === -1) { + __DEBUG_BUILD__ && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); + continue; + } + + const cpuProfile = PROFILE_QUEUE[profileIndex]; + if (!cpuProfile) { + __DEBUG_BUILD__ && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); + continue; + } + + // Remove the profile from the queue. + PROFILE_QUEUE.splice(profileIndex, 1); + const profile = createProfilingEvent(cpuProfile, profiledTransaction); + + if (profile) { + profilesToAddToEnvelope.push(profile); + } + } + + addProfilesToEnvelope(envelope, profilesToAddToEnvelope); + }); + } else { + logger.warn('[Profiling] Client does not support hooks, profiling will be disabled'); + } } /** diff --git a/packages/browser/src/profiling/jsSelfProfiling.ts b/packages/browser/src/profiling/jsSelfProfiling.ts index af9ebada8cbf..778342ba0193 100644 --- a/packages/browser/src/profiling/jsSelfProfiling.ts +++ b/packages/browser/src/profiling/jsSelfProfiling.ts @@ -1,3 +1,4 @@ +import type { DebugImage } from '@sentry/types'; // Type definitions for https://wicg.github.io/js-self-profiling/ type JSSelfProfileSampleMarker = 'script' | 'gc' | 'style' | 'layout' | 'paint' | 'other'; @@ -95,14 +96,7 @@ export interface SentryProfile { platform: string; profile: ThreadCpuProfile; debug_meta?: { - images: { - debug_id: string; - image_addr: string; - code_file: string; - type: string; - image_size: number; - image_vmaddr: string; - }[]; + images: DebugImage[]; }; transactions: { name: string; diff --git a/packages/browser/src/profiling/sendProfile.ts b/packages/browser/src/profiling/sendProfile.ts deleted file mode 100644 index 83ca990c516e..000000000000 --- a/packages/browser/src/profiling/sendProfile.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; -import { logger } from '@sentry/utils'; - -import { PROFILING_EVENT_CACHE } from './cache'; -import type { ProcessedJSSelfProfile } from './jsSelfProfiling'; -import type { ProfiledEvent } from './utils'; -import { createProfilingEventEnvelope } from './utils'; -/** - * Performs lookup in the event cache and sends the profile to Sentry. - * If the profiled transaction event is found, we use the profiled transaction event and profile - * to construct a profile type envelope and send it to Sentry. - */ -export function sendProfile(profileId: string, profile: ProcessedJSSelfProfile): void { - const event = PROFILING_EVENT_CACHE.get(profileId); - - if (!event) { - // We could not find a corresponding transaction event for this profile. - // Opt to do nothing for now, but in the future we should implement a simple retry mechanism. - if (__DEBUG_BUILD__) { - logger.log("[Profiling] Couldn't find a transaction event for this profile, dropping it."); - } - return; - } - - event.sdkProcessingMetadata = event.sdkProcessingMetadata || {}; - if (event.sdkProcessingMetadata && !event.sdkProcessingMetadata['profile']) { - event.sdkProcessingMetadata['profile'] = profile; - } - - // Client, Dsn and Transport are all required to be able to send the profiling event to Sentry. - // If either of them is not available, we remove the profile from the transaction event. - // and forward it to the next event processor. - const hub = getCurrentHub(); - const client = hub.getClient(); - - if (!client) { - if (__DEBUG_BUILD__) { - logger.log( - '[Profiling] getClient did not return a Client, removing profile from event and forwarding to next event processors.', - ); - } - return; - } - - const dsn = client.getDsn(); - if (!dsn) { - if (__DEBUG_BUILD__) { - logger.log( - '[Profiling] getDsn did not return a Dsn, removing profile from event and forwarding to next event processors.', - ); - } - return; - } - - const transport = client.getTransport(); - if (!transport) { - if (__DEBUG_BUILD__) { - logger.log( - '[Profiling] getTransport did not return a Transport, removing profile from event and forwarding to next event processors.', - ); - } - return; - } - - // If all required components are available, we construct a profiling event envelope and send it to Sentry. - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Preparing envelope and sending a profiling event'); - } - const envelope = createProfilingEventEnvelope(event as ProfiledEvent, dsn); - - // Evict event from the cache - we want to prevent the LRU cache from prioritizing already sent events over new ones. - PROFILING_EVENT_CACHE.delete(profileId); - - if (!envelope) { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Failed to construct envelope'); - } - return; - } - - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Envelope constructed, sending it'); - } - - // Wrap in try/catch because send will throw in case of a network error. - transport.send(envelope).then(null, reason => { - __DEBUG_BUILD__ && logger.log('[Profiling] Error while sending event:', reason); - }); -} diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 9fc068f7c0ee..751e7a92948a 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -1,20 +1,35 @@ -import { DEFAULT_ENVIRONMENT } from '@sentry/core'; +/* eslint-disable max-lines */ + +import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core'; import type { + DebugImage, DsnComponents, DynamicSamplingContext, + Envelope, Event, EventEnvelope, EventEnvelopeHeaders, EventItem, SdkInfo, SdkMetadata, + StackFrame, + StackParser, } from '@sentry/types'; -import { createEnvelope, dropUndefinedKeys, dsnToString, logger, uuid4 } from '@sentry/utils'; +import { + createEnvelope, + dropUndefinedKeys, + dsnToString, + forEachEnvelopeItem, + GLOBAL_OBJ, + logger, + uuid4, +} from '@sentry/utils'; import { WINDOW } from '../helpers'; import type { JSSelfProfile, JSSelfProfileStack, + ProcessedJSSelfProfile, RawThreadCpuProfile, SentryProfile, ThreadCpuProfile, @@ -187,7 +202,7 @@ export function createProfilingEventEnvelope( throw new TypeError('Profiling events may only be attached to transactions, this should never occur.'); } - const rawProfile = event.sdkProcessingMetadata['profile']; + const rawProfile = event.sdkProcessingMetadata['profile'] as ProcessedJSSelfProfile | null; if (rawProfile === undefined || rawProfile === null) { throw new TypeError( @@ -240,6 +255,9 @@ export function createProfilingEventEnvelope( architecture: OS_ARCH, is_emulator: false, }, + debug_meta: { + images: applyDebugMetadata(rawProfile.resources), + }, profile: enrichedThreadProfile, transactions: [ { @@ -290,12 +308,12 @@ export function maybeRemoveProfileFromSdkMetadata(event: Event | ProfiledEvent): * Converts a JSSelfProfile to a our sampled format. * Does not currently perform stack indexing. */ -export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): ThreadCpuProfile { +export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): SentryProfile['profile'] { let EMPTY_STACK_ID: undefined | number = undefined; let STACK_ID = 0; // Initialize the profile that we will fill with data - const profile: ThreadCpuProfile = { + const profile: SentryProfile['profile'] = { samples: [], stacks: [], frames: [], @@ -355,7 +373,7 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Threa stackTop = stackTop.parentId === undefined ? undefined : input.stacks[stackTop.parentId]; } - const sample: ThreadCpuProfile['samples'][0] = { + const sample: SentryProfile['profile']['samples'][0] = { // convert ms timestamp to ns elapsed_since_start_ns: ((jsSample.timestamp - start) * MS_TO_NS).toFixed(0), stack_id: STACK_ID, @@ -369,3 +387,148 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Threa return profile; } + +/** + * Adds items to envelope if they are not already present - mutates the envelope. + * @param envelope + */ +export function addProfilesToEnvelope(envelope: Envelope, profiles: SentryProfile[]): Envelope { + if (!profiles.length) { + return envelope; + } + + for (const profile of profiles) { + // @ts-ignore untyped envelope + envelope[1].push([{ type: 'profile' }, profile]); + } + return envelope; +} + +/** + * Finds transactions with profile_id context in the envelope + * @param envelope + * @returns + */ +export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[] { + const events: Event[] = []; + + forEachEnvelopeItem(envelope, (item, type) => { + if (type !== 'transaction') { + return; + } + + for (let j = 1; j < item.length; j++) { + const event = item[j] as Event; + + if (event && event.contexts && event.contexts['profile'] && event.contexts['profile']['profile_id']) { + events.push(item[j] as Event); + } + } + }); + + return events; +} + +const debugIdStackParserCache = new WeakMap>(); +/** + * Applies debug meta data to an event from a list of paths to resources (sourcemaps) + */ +export function applyDebugMetadata(resource_paths: ReadonlyArray): DebugImage[] { + const debugIdMap = GLOBAL_OBJ._sentryDebugIds; + + if (!debugIdMap) { + return []; + } + + const hub = getCurrentHub(); + if (!hub) { + return []; + } + const client = hub.getClient(); + if (!client) { + return []; + } + const options = client.getOptions(); + if (!options) { + return []; + } + const stackParser = options.stackParser; + if (!stackParser) { + return []; + } + + let debugIdStackFramesCache: Map; + const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); + if (cachedDebugIdStackFrameCache) { + debugIdStackFramesCache = cachedDebugIdStackFrameCache; + } else { + debugIdStackFramesCache = new Map(); + debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); + } + + // Build a map of filename -> debug_id + const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { + let parsedStack: StackFrame[]; + + const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); + if (cachedParsedStack) { + parsedStack = cachedParsedStack; + } else { + parsedStack = stackParser(debugIdStackTrace); + debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); + } + + for (let i = parsedStack.length - 1; i >= 0; i--) { + const stackFrame = parsedStack[i]; + const file = stackFrame && stackFrame.filename; + + if (stackFrame && file) { + acc[file] = debugIdMap[debugIdStackTrace] as string; + break; + } + } + return acc; + }, {}); + + const images: DebugImage[] = []; + for (const path of resource_paths) { + if (path && filenameDebugIdMap[path]) { + images.push({ + type: 'sourcemap', + code_file: path, + debug_id: filenameDebugIdMap[path] as string, + }); + } + } + + return images; +} + +/** + * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1). + */ +export function isValidSampleRate(rate: unknown): boolean { + // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck + if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) { + __DEBUG_BUILD__ && + logger.warn( + `[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( + rate, + )} of type ${JSON.stringify(typeof rate)}.`, + ); + return false; + } + + // Boolean sample rates are always valid + if (rate === true || rate === false) { + return true; + } + + // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false + if (rate < 0 || rate > 1) { + __DEBUG_BUILD__ && + logger.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`); + return false; + } + return true; +} diff --git a/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts b/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts new file mode 100644 index 000000000000..279cb720cfb4 --- /dev/null +++ b/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts @@ -0,0 +1,47 @@ +const Benchmark = require('benchmark'); + +const fns = require('./dedupePerformanceEntries'); +const fnsNew = require('./dedupePerformanceEntries.new'); + +const data = JSON.parse( + '[{"name":"https://sentry.sentry.io/issues/","entryType":"navigation","startTime":0,"duration":1316.5,"initiatorType":"navigation","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":464.7000000476837,"domainLookupStart":466.89999997615814,"domainLookupEnd":498.2000000476837,"connectStart":498.2000000476837,"secureConnectionStart":530.6000000238419,"connectEnd":602.3999999761581,"requestStart":602.3999999761581,"responseStart":951.3000000715256,"responseEnd":957.3000000715256,"transferSize":7984,"encodedBodySize":7684,"decodedBodySize":19215,"responseStatus":200,"serverTiming":[],"unloadEventStart":0,"unloadEventEnd":0,"domInteractive":1056.3999999761581,"domContentLoadedEventStart":1056.3999999761581,"domContentLoadedEventEnd":1056.5,"domComplete":1315.1000000238419,"loadEventStart":1315.1000000238419,"loadEventEnd":1316.5,"type":"navigate","redirectCount":0,"activationStart":0},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/entrypoints-hashed/sentry-TynoRHRu_eq0ob3bedRRtUK-KuSAERzrmgEKRbzAXCI.css","entryType":"resource","startTime":963.8999999761581,"duration":20.700000047683716,"initiatorType":"link","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":963.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":984.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://js.sentry-cdn.com/c51734c603c4430eb57cb0a5728a479d.min.js","entryType":"resource","startTime":964.1000000238419,"duration":23.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":987.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/entrypoints-hashed/app-CcPyCILeKKdnKtN3PEQ8pUuiC3YiM7FoxWRZaq-pv8A.js","entryType":"resource","startTime":964.2000000476837,"duration":24.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":988.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/541fb42adc5d8c91924cc9755b0ae223/sentry/js/ads.js","entryType":"resource","startTime":964.3000000715256,"duration":23.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":987.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://js.stripe.com/v3/","entryType":"resource","startTime":964.3999999761581,"duration":322.7000000476837,"initiatorType":"script","nextHopProtocol":"h2","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.3999999761581,"domainLookupStart":1033.8999999761581,"domainLookupEnd":1045.2000000476837,"connectStart":1045.2000000476837,"secureConnectionStart":1122.2000000476837,"connectEnd":1203.3000000715256,"requestStart":1203.5,"responseStart":1284.1000000238419,"responseEnd":1287.1000000238419,"transferSize":300,"encodedBodySize":127071,"decodedBodySize":474393,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/541fb42adc5d8c91924cc9755b0ae223/sentry/images/sentry-loader.svg","entryType":"resource","startTime":964.5,"duration":75.70000004768372,"initiatorType":"img","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1040.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://static.zdassets.com/ekr/snippet.js?key=99cb65b7-1616-43cd-b8be-203f91ec932e","entryType":"resource","startTime":964.5,"duration":68.80000007152557,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1033.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"head-start","entryType":"mark","startTime":1030.3000000715256,"duration":0},{"name":"app.page.body-load","entryType":"measure","startTime":1030.3000000715256,"duration":25.5},{"name":"app.page.bundle-load","entryType":"measure","startTime":1030.3000000715256,"duration":217.69999992847443},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_bootstrap_index_tsx.b99c6b7cd539e00e7e04.js","entryType":"resource","startTime":1033.6000000238419,"duration":7.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1041.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_sentry_core_esm_exports_js-node_modules_moment_moment_js.2a74ee6a7220aedbc010.js","entryType":"resource","startTime":1033.8000000715256,"duration":8.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1042,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_emotion_react_jsx-runtime_dist_emotion-react-jsx-runtime_browser_esm_js--5816c0.e8a1be9dbda970aac154.js","entryType":"resource","startTime":1033.8999999761581,"duration":12.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1046.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/node_modules_emotion_unitless_dist_emotion-unitless_esm_js-app_bootstrap_initializeLocale_tsx-a2b5a60.3df912d3033a82371b06.js","entryType":"resource","startTime":1034.1000000238419,"duration":11.899999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1034.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1046,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://reload.getsentry.net/page/","entryType":"resource","startTime":1037.7000000476837,"duration":174.29999995231628,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1037.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1212,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"head-end","entryType":"mark","startTime":1038.3999999761581,"duration":0},{"name":"https://cdn.pendo.io/agent/static/dc5c6fad-c3ae-4441-49ce-0ae37103aed7/pendo.js","entryType":"resource","startTime":1038.3999999761581,"duration":103.80000007152557,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1038.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1142.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://ekr.zdassets.com/compose/99cb65b7-1616-43cd-b8be-203f91ec932e","entryType":"resource","startTime":1055.7000000476837,"duration":127.39999997615814,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1055.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1183.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"body-end","entryType":"mark","startTime":1055.8000000715256,"duration":0},{"name":"first-paint","entryType":"paint","startTime":1058.8000000715256,"duration":0},{"name":"first-contentful-paint","entryType":"paint","startTime":1058.8000000715256,"duration":0},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_internationalized_string_dist_module_js-node_modules_react-aria_focus_di-544641.75d2e5586f6ac68b95f3.js","entryType":"resource","startTime":1062,"duration":4.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1066.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_button_dist_module_js-node_modules_react-aria_gridlist_dist_m-81a0c4.bb623b51efc44bf23e31.js","entryType":"resource","startTime":1062.2000000476837,"duration":6.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1068.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_radio_dist_module_js-node_modules_react-stately_list_dist_mod-7bc4f5.ce5e7d7fd11388c76110.js","entryType":"resource","startTime":1062.5,"duration":4.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1067.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_selection_dist_module_js-node_modules_lodash_cloneDeep_js-nod-d70379.d688b691982475d8de57.js","entryType":"resource","startTime":1062.8999999761581,"duration":7,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1069.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_date-fns_format_index_js.ecf7cb2fa6edc821f4eb.js","entryType":"resource","startTime":1063.1000000238419,"duration":5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1068.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_buffer_index_js.dcacea8575981fed3f5c.js","entryType":"resource","startTime":1063.3000000715256,"duration":5.899999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1069.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_echarts-for-react_lib_core_js-node_modules_echarts_core_js-node_modules_-be3c62.ed7280025d8653d5ad4a.js","entryType":"resource","startTime":1063.5,"duration":24.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1087.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_moment-timezone_index_js-node_modules_reflux_src_index_js.9cc3b3392494216b18c8.js","entryType":"resource","startTime":1063.6000000238419,"duration":22.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1086.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_emotion_is-prop-valid_dist_is-prop-valid_browser_esm_js-node_modules_emo-f27c2a.c8a681442de6bd91116a.js","entryType":"resource","startTime":1063.7000000476837,"duration":27.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1091,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_core-js_modules_es_array_at_js-node_modules_core-js_modules_es_string_at-ac4873.6673d5eace8f33b8e81b.js","entryType":"resource","startTime":1063.8000000715256,"duration":7.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1071.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_js-beautify_js_index_js.0a8d3ce59a51dc11f6fd.js","entryType":"resource","startTime":1063.8000000715256,"duration":14,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1077.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_history_lib_createMemoryH-9f114c.82628466bbc3011927af.js","entryType":"resource","startTime":1063.8999999761581,"duration":14.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_interactions_dist_module_js-node_modules_react-aria_tabs_dist-0dd1d1.da9ab3a97943160dcae2.js","entryType":"resource","startTime":1064.1000000238419,"duration":12.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-getsentry_node_modules_lodash__baseClone_js-getsentry_node_modules_lodash__baseIsEqua-18ca52.1a0051e2efb17920535c.js","entryType":"resource","startTime":1064.3000000715256,"duration":10.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1074.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-getsentry_node_modules_amplitude_analytics-browser_lib_esm_index_js-getsentry_node_mo-2275d3.394f54209591e3d776cb.js","entryType":"resource","startTime":1064.3999999761581,"duration":12.400000095367432,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_featureFeedback_feedbackModal_tsx.cfb6a3f320673aceab2f.js","entryType":"resource","startTime":1064.5,"duration":10.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1075.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_tag_tsx-app_components_textOverflow_tsx-app_components_timeSince_tsx-app_utils-b137eb.f41224d66223c5b07f08.js","entryType":"resource","startTime":1064.8999999761581,"duration":10.800000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1075.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_acl_feature_tsx-app_components_assistant_guideAnchor_tsx-app_components_clipbo-a9c4fa.b0ca9f703fe2c2389484.js","entryType":"resource","startTime":1065,"duration":13.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_events_interfaces_frame_utils_tsx-app_components_segmentedControl_tsx.036777a408266806394e.js","entryType":"resource","startTime":1065.2000000476837,"duration":10.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_asyncComponent_tsx.4e8e1eece3af7680e5b0.js","entryType":"resource","startTime":1065.3999999761581,"duration":10.900000095367432,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_guides_tsx-app_components_charts_utils_tsx-app_utils_discover_fieldRendere-6404c2.57f08970b136b7ab5492.js","entryType":"resource","startTime":1065.3999999761581,"duration":23.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_searchSyntax_parser_tsx-app_components_searchSyntax_utils_tsx-app_utils_withPa-338bbb.30bc1943da4795e58e13.js","entryType":"resource","startTime":1065.7000000476837,"duration":12,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1077.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_navigation_tsx-app_components_pagination_tsx-app_utils_useRouter_tsx-app_u-e3e402.a492cd094ceb8cebdc8b.js","entryType":"resource","startTime":1065.8000000715256,"duration":12.399999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_alerts_toastIndicator_tsx-app_components_assistant_getGuidesContent_tsx-app_co-3627de.49c1887d6fb6ab1bf842.js","entryType":"resource","startTime":1065.8999999761581,"duration":22.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1088.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_assigneeSelector_tsx-app_components_charts_barChart_tsx-app_components_organiz-1c0127.5cc379e8a2e3c80c8d3d.js","entryType":"resource","startTime":1066,"duration":17.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1083.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_eventOrGroupHeader_tsx-app_utils_useParams_tsx.0ae6c7e8cf1492bfec03.js","entryType":"resource","startTime":1066.3999999761581,"duration":13.100000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1079.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_deprecatedforms_selectField_tsx-app_components_forms_apiForm_tsx-app_component-fe94fb.91f9f63fa45bc4790767.js","entryType":"resource","startTime":1066.3999999761581,"duration":18.40000009536743,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1084.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_onboarding_documentationWrapper_tsx-app_components_onboarding_footer_tsx-app_v-7ba665.54c11556cadee8f8e5cc.js","entryType":"resource","startTime":1066.5,"duration":13.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_actions_button_tsx-app_components_dropdownAutoComplete_autoCompleteFilter_tsx--91f7e8.77d1c40bdaa6bb9db5d4.js","entryType":"resource","startTime":1066.7000000476837,"duration":14,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_banner_tsx-app_components_charts_optionSelector_tsx.bc62abd018a73f0f63f7.js","entryType":"resource","startTime":1066.7000000476837,"duration":14.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_account_tsx-app_actionCreators_discoverSavedQueries_tsx-app_components_eve-338d0c.cada450527f8ceb0f451.js","entryType":"resource","startTime":1067.1000000238419,"duration":22.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_views_onboarding_setupDocs_tsx.e47b4c53ba4bbfa504e7.js","entryType":"resource","startTime":1067.3000000715256,"duration":14.099999904632568,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1081.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_views_organizationStats_usageStatsOrg_tsx.d3d29fe5626988e28762.js","entryType":"resource","startTime":1067.3999999761581,"duration":14.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1081.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/getsentry_static_getsentry_gsApp_registerHooks_tsx-app_components_codeSnippet_tsx-getsentry_s-61ae35.7e6a34df145cbecac219.js","entryType":"resource","startTime":1067.3999999761581,"duration":19.90000009536743,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1087.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/getsentry_static_getsentry_gsApp_initializeBundleMetrics_tsx.9fe7b144490c0ce9c4c6.js","entryType":"resource","startTime":1067.6000000238419,"duration":14.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1082.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/node_modules_emotion_unitless_dist_emotion-unitless_esm_js-app_bootstrap_initializeMain_tsx-n-f02164.cb9d501fc041dc503ed2.js","entryType":"resource","startTime":1067.6000000238419,"duration":21.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_plugins_index_tsx.929e41736b0670e5e0ba.js","entryType":"resource","startTime":1226.1000000238419,"duration":1.399999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1226.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1227.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_bootstrap_initializeApp_tsx.1cbf31514ffcc3a3528f.js","entryType":"resource","startTime":1226.2000000476837,"duration":1.7999999523162842,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1226.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1228,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"sentry-tracing-init","entryType":"mark","startTime":1243,"duration":0},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":1247.8999999761581,"duration":74.10000002384186,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1247.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1322,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"sentry-app-init","entryType":"mark","startTime":1248,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/?detailed=0","entryType":"resource","startTime":1259.7000000476837,"duration":262.2999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1259.7000000476837,"domainLookupStart":1259.7000000476837,"domainLookupEnd":1259.7000000476837,"connectStart":1259.7000000476837,"secureConnectionStart":1259.7000000476837,"connectEnd":1259.7000000476837,"requestStart":1260.8999999761581,"responseStart":1520.7000000476837,"responseEnd":1522,"transferSize":4982,"encodedBodySize":4682,"decodedBodySize":15837,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/projects/?all_projects=1&collapse=latestDeploys","entryType":"resource","startTime":1260.1000000238419,"duration":611.7999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1260.1000000238419,"domainLookupStart":1262.3999999761581,"domainLookupEnd":1262.3999999761581,"connectStart":1262.3999999761581,"secureConnectionStart":1293.6000000238419,"connectEnd":1332.6000000238419,"requestStart":1333,"responseStart":1870.7000000476837,"responseEnd":1871.8999999761581,"transferSize":23383,"encodedBodySize":23083,"decodedBodySize":148289,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/teams/","entryType":"resource","startTime":1260.3000000715256,"duration":547.6999999284744,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1260.3000000715256,"domainLookupStart":1261.2000000476837,"domainLookupEnd":1261.2000000476837,"connectStart":1261.2000000476837,"secureConnectionStart":1294,"connectEnd":1332.8000000715256,"requestStart":1333.1000000238419,"responseStart":1806.7000000476837,"responseEnd":1808,"transferSize":18228,"encodedBodySize":17928,"decodedBodySize":205169,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/?member=1","entryType":"resource","startTime":1261.3999999761581,"duration":371.40000009536743,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1261.3999999761581,"domainLookupStart":1263.2000000476837,"domainLookupEnd":1263.2000000476837,"connectStart":1263.2000000476837,"secureConnectionStart":1295.3000000715256,"connectEnd":1332.8999999761581,"requestStart":1333.2000000476837,"responseStart":1631.8000000715256,"responseEnd":1632.8000000715256,"transferSize":3486,"encodedBodySize":3186,"decodedBodySize":28039,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/internal/health/","entryType":"resource","startTime":1261.6000000238419,"duration":128.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1261.6000000238419,"domainLookupStart":1262.2000000476837,"domainLookupEnd":1262.3000000715256,"connectStart":1262.3000000715256,"secureConnectionStart":1295.3999999761581,"connectEnd":1332.8999999761581,"requestStart":1333.2000000476837,"responseStart":1388.7000000476837,"responseEnd":1390,"transferSize":300,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/assistant/","entryType":"resource","startTime":1262.5,"duration":158.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1262.5,"domainLookupStart":1262.8999999761581,"domainLookupEnd":1262.8999999761581,"connectStart":1262.8999999761581,"secureConnectionStart":1295,"connectEnd":1332.8999999761581,"requestStart":1333.3000000715256,"responseStart":1419.2000000476837,"responseEnd":1420.8999999761581,"transferSize":1009,"encodedBodySize":709,"decodedBodySize":709,"responseStatus":200,"serverTiming":[]},{"name":"@grammarly-extension:checkScriptInitStart","entryType":"mark","startTime":1302.8000000715256,"duration":0},{"name":"@grammarly-extension:checkScriptInitEnd","entryType":"mark","startTime":1303.8000000715256,"duration":0},{"name":"https://js.stripe.com/v3/m-outer-93afeeb17bc37e711759584dbfc50d47.html#url=https%3A%2F%2Fsentry.sentry.io%2Fissues%2F&title=Sentry&referrer=&muid=78f25c63-c9aa-49b3-b21e-d24aa1a9e8a524eeea&sid=NA&version=6&preview=false","entryType":"resource","startTime":1316.5,"duration":5.899999976158142,"initiatorType":"iframe","nextHopProtocol":"h2","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1316.5,"domainLookupStart":1316.5,"domainLookupEnd":1316.5,"connectStart":1316.5,"secureConnectionStart":1316.5,"connectEnd":1316.5,"requestStart":1319.3000000715256,"responseStart":1319.7000000476837,"responseEnd":1322.3999999761581,"transferSize":0,"encodedBodySize":122,"decodedBodySize":200,"responseStatus":0,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/client-state/","entryType":"resource","startTime":1523.8000000715256,"duration":247.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1523.8000000715256,"domainLookupStart":1523.8000000715256,"domainLookupEnd":1523.8000000715256,"connectStart":1523.8000000715256,"secureConnectionStart":1523.8000000715256,"connectEnd":1523.8000000715256,"requestStart":1524.6000000238419,"responseStart":1770.5,"responseEnd":1771.3000000715256,"transferSize":302,"encodedBodySize":2,"decodedBodySize":2,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/subscriptions/sentry/","entryType":"resource","startTime":1538.5,"duration":238.20000004768372,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1538.5,"domainLookupStart":1538.5,"domainLookupEnd":1538.5,"connectStart":1538.5,"secureConnectionStart":1538.5,"connectEnd":1538.5,"requestStart":1541.8999999761581,"responseStart":1775.3000000715256,"responseEnd":1776.7000000476837,"transferSize":2094,"encodedBodySize":1794,"decodedBodySize":5762,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/broadcasts/","entryType":"resource","startTime":1538.8999999761581,"duration":238.20000004768372,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1538.8999999761581,"domainLookupStart":1538.8999999761581,"domainLookupEnd":1538.8999999761581,"connectStart":1538.8999999761581,"secureConnectionStart":1538.8999999761581,"connectEnd":1538.8999999761581,"requestStart":1633,"responseStart":1775.3000000715256,"responseEnd":1777.1000000238419,"transferSize":1105,"encodedBodySize":805,"decodedBodySize":805,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/promotions/trigger-check/","entryType":"resource","startTime":1540.7000000476837,"duration":139.79999995231628,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1540.7000000476837,"domainLookupStart":1540.7000000476837,"domainLookupEnd":1540.7000000476837,"connectStart":1540.7000000476837,"secureConnectionStart":1540.7000000476837,"connectEnd":1540.7000000476837,"requestStart":1542.2000000476837,"responseStart":1680,"responseEnd":1680.5,"transferSize":398,"encodedBodySize":98,"decodedBodySize":98,"responseStatus":200,"serverTiming":[]},{"name":"https://t687h3m0nh65.statuspage.io/api/v2/incidents/unresolved.json","entryType":"resource","startTime":1541.1000000238419,"duration":130.89999997615814,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1541.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1672,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/tags/?statsPeriod=14d&use_cache=1","entryType":"resource","startTime":1544.3000000715256,"duration":1457.3999999761581,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.3000000715256,"domainLookupStart":1544.3000000715256,"domainLookupEnd":1544.3000000715256,"connectStart":1544.3000000715256,"secureConnectionStart":1544.3000000715256,"connectEnd":1544.3000000715256,"requestStart":1680.8000000715256,"responseStart":2996.100000023842,"responseEnd":3001.7000000476837,"transferSize":7419,"encodedBodySize":7119,"decodedBodySize":25782,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/users/","entryType":"resource","startTime":1544.6000000238419,"duration":2509.7999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.6000000238419,"domainLookupStart":1544.6000000238419,"domainLookupEnd":1544.6000000238419,"connectStart":1544.6000000238419,"secureConnectionStart":1544.6000000238419,"connectEnd":1544.6000000238419,"requestStart":1771.7000000476837,"responseStart":3961.8000000715256,"responseEnd":4054.399999976158,"transferSize":45219,"encodedBodySize":44919,"decodedBodySize":261770,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/searches/","entryType":"resource","startTime":1544.8999999761581,"duration":374.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.8999999761581,"domainLookupStart":1544.8999999761581,"domainLookupEnd":1544.8999999761581,"connectStart":1544.8999999761581,"secureConnectionStart":1544.8999999761581,"connectEnd":1544.8999999761581,"requestStart":1777.3999999761581,"responseStart":1918.6000000238419,"responseEnd":1919.3999999761581,"transferSize":1445,"encodedBodySize":1145,"decodedBodySize":4268,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/organization-avatar/24f6f762f7a7473888b259c566da5adb/?s=120","entryType":"resource","startTime":1545.3000000715256,"duration":290.7999999523163,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1545.3000000715256,"domainLookupStart":1545.3000000715256,"domainLookupEnd":1545.3000000715256,"connectStart":1545.3000000715256,"secureConnectionStart":1545.3000000715256,"connectEnd":1545.3000000715256,"requestStart":1777.5,"responseStart":1835.3999999761581,"responseEnd":1836.1000000238419,"transferSize":6603,"encodedBodySize":6303,"decodedBodySize":6303,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":1600,"duration":38.300000071525574,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1600,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1638.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/prompts-activity/?feature=deactivated_member_alert&feature=errors_overage_alert&feature=attachments_overage_alert&feature=transactions_overage_alert&feature=replays_overage_alert&feature=errors_warning_alert&feature=attachments_warning_alert&feature=transactions_warning_alert&feature=replays_warning_alert&organization_id=1","entryType":"resource","startTime":1780.8999999761581,"duration":91.60000002384186,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1780.8999999761581,"domainLookupStart":1780.8999999761581,"domainLookupEnd":1780.8999999761581,"connectStart":1780.8999999761581,"secureConnectionStart":1780.8999999761581,"connectEnd":1780.8999999761581,"requestStart":1808.3000000715256,"responseStart":1872.2000000476837,"responseEnd":1872.5,"transferSize":315,"encodedBodySize":15,"decodedBodySize":15,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/pendo-details/","entryType":"resource","startTime":1791.5,"duration":219.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1791.5,"domainLookupStart":1791.5,"domainLookupEnd":1791.5,"connectStart":1791.5,"secureConnectionStart":1791.5,"connectEnd":1791.5,"requestStart":1836.3999999761581,"responseStart":2010.2000000476837,"responseEnd":2011,"transferSize":1092,"encodedBodySize":792,"decodedBodySize":792,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/processingissues/","entryType":"resource","startTime":1962.7000000476837,"duration":235.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1962.7000000476837,"domainLookupStart":1962.7000000476837,"domainLookupEnd":1962.7000000476837,"connectStart":1962.7000000476837,"secureConnectionStart":1962.7000000476837,"connectEnd":1962.7000000476837,"requestStart":1963.7000000476837,"responseStart":2196.5,"responseEnd":2198.100000023842,"transferSize":676,"encodedBodySize":376,"decodedBodySize":3988,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues/?collapse=stats&expand=owners&expand=inbox&limit=25&query=is%3Aunresolved&shortIdLookup=1&statsPeriod=14d","entryType":"resource","startTime":1963.3000000715256,"duration":314.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1963.3000000715256,"domainLookupStart":1963.3000000715256,"domainLookupEnd":1963.3000000715256,"connectStart":1963.3000000715256,"secureConnectionStart":1963.3000000715256,"connectEnd":1963.3000000715256,"requestStart":1963.8999999761581,"responseStart":2275.7000000476837,"responseEnd":2277.8000000715256,"transferSize":5092,"encodedBodySize":4792,"decodedBodySize":27778,"responseStatus":200,"serverTiming":[]},{"name":"https://data.pendo.io/data/ptm.gif/dc5c6fad-c3ae-4441-49ce-0ae37103aed7?v=2.181.0_prod&ct=1682429095854&jzb=eJwtj81qwzAQhN9lz8a2_KPKPpdAoJAWUnooJcixQmQcS6xWLibk3bN1rYtWMzv6mO870OINtDA63UMCHbrfYPBE9saqkKqoiiZvalU1Ccw2WHJ4sj0HRCpkpQpZckqfzy5OtBksRBx5uhL50GZZMBPhkm6XdZkNIZqQ8aJH5wO095W_UVWdp2WznhcpaiUevKeR00fd7RkxxXFMgP4fcCx28eNd7N6G2WtzqPjXC-qbWc3XfP4U-dD1l3L_Ja7yr-JChpFc7PHzBAHgTzM","entryType":"resource","startTime":2030.8000000715256,"duration":123.19999992847443,"initiatorType":"img","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2030.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2154,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://data.pendo.io/data/guide.js/dc5c6fad-c3ae-4441-49ce-0ae37103aed7?id=4&jzb=eJxtVE1zo0gM_S-csQ3kYzK-eexkK1WTneyQvezFJdMCK2m6KXXDlHdr_vuoAUPGmxP0k1p6kp76v6gjR97yo4rWUbpMb6_vsturKI6gKGxr_IDLuWUtf0fvG7derRwaz6fl-CG7IudadCtxrNGDAg_RegoefukyQetwzDpBbDUKAKomI0dyO-C3J6sELEE7jCOn3v6EGp14jbkrG49_r9CBK5gavzyw_SHh4-oIi8H6gRMjFP6MNyd_tGapXsFMEReFpgt7qcG9fRDLBJJxdASXi-2FwTgJTtYIU8-tMB9NOTr3AfwdGw2n9-gTeqZio5H9BBeS5dsPg-y2YTbR-iaOGravWPgRyK7iqCR2_r4LPKgO_cySNFsk6SK9fsmydXK3vsmWn9L0Lvn8T9_leyON16geTV5LrnOr6yELn7b9BEyrdc_sGbm0XIMp8LGf-jvWz2xL0hM-hurF0ZfyvdU4Ur39NFbfN2RowDmSlim0UAXyaPZ_fIl-ToKctdRzf2Cc1UHuCYxcE_MQh9wLE4hwJ4d745EbJoeXlndlPUv-S3PeugaNCrFHrLh0EiBHXebI3cxJyjJB03W2P7SOjJS7l9HsoS2lgGB9IZQNCR7DNID1aaNsI0TPdXjrQT9hfZDRi2tHsig-LAyKyjtU98y2t7Dwm-HfdThfAw4JB9-xrVs5kTR3REV7oHeiyK9YCjrM3nIFhv6FEG8ThnOV3IjeZIjfzA6lcyoPLZr7IWlJIT9Y3rm_G4daejUUFEh1KGqpbYh2ziBiHoA59SINgepGmKOaLoRyhGYp0--VPtD__Ra5r-D89lRoFAZBKL3rLEsl7jmFeZ_DbjXIykgFfdbWeVsj72wNNG8siYIq7pvQP0Maire4In9sD_ErMcSNKJBV609xh1ygnoHQ7tYfJV0X-hKuV9ZW8ugFMu54sMBqXI_0cyIgucJKlBxkmn-1GHaxt17filUa_4W0JlNtlJKZz5X9z_KA4FuWkV1sqni8w4a35UhNWNOxYNm8Blj6tpn2TyB54PuX5WaVXK-yJAuvt_AMmxzgZXqXLpO9jFNFP3_-Ams9Pkk&v=2.181.0_prod&ct=1682429095856","entryType":"resource","startTime":2032.7000000476837,"duration":142.10000002384186,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2032.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2174.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":2270.2000000476837,"duration":38.199999928474426,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2270.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2308.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2295.2000000476837,"duration":0},{"name":"IssueList-Body-vcsd-end-pre-timeout","entryType":"mark","startTime":2297,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues-stats/?groups=3622378978&groups=4050674861&groups=3899312585&groups=3915010727&groups=3899249705&groups=4057453162&groups=3935262512&groups=3935139508&groups=4057455327&groups=4012287646&groups=3920436468&groups=3991114523&groups=4046641574&groups=4090532740&groups=3670893465&groups=3899249419&groups=4077639192&groups=4074870668&groups=3899261560&groups=3919710734&groups=3248727627&groups=4113029290&groups=3911631555&groups=3740335939&groups=3899257705&query=is%3Aunresolved&statsPeriod=14d","entryType":"resource","startTime":2304.5,"duration":696.6000000238419,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2304.5,"domainLookupStart":2304.5,"domainLookupEnd":2304.5,"connectStart":2304.5,"secureConnectionStart":2304.5,"connectEnd":2304.5,"requestStart":2305.7000000476837,"responseStart":2995.399999976158,"responseEnd":3001.100000023842,"transferSize":4468,"encodedBodySize":4168,"decodedBodySize":17200,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2314.5,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues-count/?query=is%3Aunresolved%20is%3Afor_review%20assigned_or_suggested%3A%5Bme%2C%20none%5D&query=is%3Aignored&query=is%3Areprocessing&statsPeriod=14d","entryType":"resource","startTime":2320.399999976158,"duration":707.3000000715256,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2320.399999976158,"domainLookupStart":2320.399999976158,"domainLookupEnd":2320.399999976158,"connectStart":2320.399999976158,"secureConnectionStart":2320.399999976158,"connectEnd":2320.399999976158,"requestStart":2321.399999976158,"responseStart":3026.3000000715256,"responseEnd":3027.7000000476837,"transferSize":405,"encodedBodySize":105,"decodedBodySize":105,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2328.3000000715256,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/replay-count/?project=11276&query=issue.id%3A%5B3740335939%5D&statsPeriod=14d","entryType":"resource","startTime":2386,"duration":166.30000007152557,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2386,"domainLookupStart":2386,"domainLookupEnd":2386,"connectStart":2386,"secureConnectionStart":2386,"connectEnd":2386,"requestStart":2388.7000000476837,"responseStart":2551.600000023842,"responseEnd":2552.3000000715256,"transferSize":317,"encodedBodySize":17,"decodedBodySize":17,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2399.100000023842,"duration":0},{"name":"VCD [IssueList-Body] #1","entryType":"measure","startTime":2399.100000023842,"duration":50},{"name":"IssueList-Body-vcsd-end","entryType":"mark","startTime":2449.100000023842,"duration":0},{"name":"https://api2.amplitude.com/2/httpapi","entryType":"resource","startTime":2457,"duration":542.3999999761581,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2457,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2999.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://reload.getsentry.net/event/","entryType":"resource","startTime":3219.7000000476837,"duration":33.89999997615814,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":3219.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":3253.600000023842,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":3251.100000023842,"duration":0},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":3316.2000000476837,"duration":36.799999952316284,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":3316.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":3353,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":3326.7000000476837,"duration":0},{"name":"keydown","entryType":"first-input","startTime":4007.899999976158,"duration":8,"processingStart":4011.600000023842,"processingEnd":4013,"cancelable":true},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":4077.600000023842,"duration":0},{"name":"https://sentry.sentry.io/avatar/ca66558c539e408eaaa450a096e24ebf/?s=120","entryType":"resource","startTime":4166,"duration":92.5,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4166,"domainLookupStart":4166,"domainLookupEnd":4166,"connectStart":4166,"secureConnectionStart":4166,"connectEnd":4166,"requestStart":4168.300000071526,"responseStart":4255.200000047684,"responseEnd":4258.5,"transferSize":6105,"encodedBodySize":5805,"decodedBodySize":5805,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/avatar/ba59b4e18344480794c5a623dbf6ef6a/?s=120","entryType":"resource","startTime":4166.100000023842,"duration":101,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4166.100000023842,"domainLookupStart":4166.100000023842,"domainLookupEnd":4166.100000023842,"connectStart":4166.100000023842,"secureConnectionStart":4166.100000023842,"connectEnd":4166.100000023842,"requestStart":4167.700000047684,"responseStart":4266.5,"responseEnd":4267.100000023842,"transferSize":21805,"encodedBodySize":21505,"decodedBodySize":21505,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":4175,"duration":0},{"name":"https://api2.amplitude.com/2/httpapi","entryType":"resource","startTime":4317,"duration":258.7000000476837,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4317,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4575.700000047684,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":4323.800000071526,"duration":34.799999952316284,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4323.800000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4358.600000023842,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":4454.5,"duration":75.39999997615814,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4454.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4529.899999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":5931.800000071526,"duration":69.19999992847443,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":5931.800000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":6001,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":9973.300000071526,"duration":45.09999990463257,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":9973.300000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":10018.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":68177.70000004768,"duration":100.19999992847443,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":68177.70000004768,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":68277.89999997616,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]}]', +); + +const half = data.slice(0, Math.round(data.length / 2)); +// add tests +new Benchmark.Suite() + .add('new list (old)', function () { + const entries = fns.dedupePerformanceEntries([], data); + }) + .add('new list (new)', function () { + const entries = fnsNew.dedupePerformanceEntries([], data); + }) + .add('diff list half (old)', function () { + const entries = fns.dedupePerformanceEntries(half, data); + }) + .add('diff list half (new)', function () { + const entries = fnsNew.dedupePerformanceEntries(half, data); + }) + .add('same list (old)', function () { + const entries = fns.dedupePerformanceEntries(data, data); + }) + .add('same list half (new)', function () { + const entries = fnsNew.dedupePerformanceEntries(data, data); + }) + // @ts-ignore + .on('error', function (event) { + // @ts-ignore + console.log(event.target.error); + }) + // @ts-ignore + .on('cycle', function (event) { + // @ts-ignore + console.log(String(event.target)); + }) + .on('complete', function () { + // @ts-ignore + console.log(`Fastest is ${this.filter('fastest').map('name')}`); + }) + .run({ async: false }); + +module.exports = {}; diff --git a/packages/replay/src/util/dedupePerformanceEntries.new.ts b/packages/replay/src/util/dedupePerformanceEntries.new.ts new file mode 100644 index 000000000000..94d430a78182 --- /dev/null +++ b/packages/replay/src/util/dedupePerformanceEntries.new.ts @@ -0,0 +1,102 @@ +import type { PerformanceNavigationTiming, PerformancePaintTiming } from '../types'; + +const NAVIGATION_ENTRY_KEYS: Array = [ + 'name', + 'type', + 'startTime', + 'transferSize', + 'duration', +]; + +/** + * There are some difficulties diagnosing why there are duplicate navigation + * entries. We've witnessed several intermittent results: + * - duplicate entries have duration = 0 + * - duplicate entries are the same object reference + * - none of the above + * + * Compare the values of several keys to determine if the entries are duplicates or not. + */ +// TODO (high-prio): Figure out wth is returned here +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +function dedupePerformanceEntries( + currentList: PerformanceEntryList, + newList: PerformanceEntryList, +): PerformanceEntryList { + if (!currentList.length && !newList.length) { + return []; + } + + // if (currentList.length === 0) { + // return newList.slice().sort((a, b) => a.startTime - b.startTime); + // } + + // Partition `currentList` into 3 different lists based on entryType + const [existingNavigationEntries, existingLcpEntries, existingEntries] = currentList.reduce( + (acc: [PerformanceNavigationTiming[], PerformancePaintTiming[], PerformanceEntryList], entry) => { + if (entry.entryType === 'navigation') { + acc[0].push(entry as PerformanceNavigationTiming); + } else if (entry.entryType === 'largest-contentful-paint') { + acc[1].push(entry as PerformancePaintTiming); + } else { + acc[2].push(entry); + } + return acc; + }, + [[], [], []], + ); + + const newEntries: PerformanceEntryList = []; + const newNavigationEntries: PerformanceNavigationTiming[] = []; + let newLcpEntry: PerformancePaintTiming | undefined = existingLcpEntries[existingLcpEntries.length - 1]; // Take the last element as list is sorted + let foundNewLcp = false; + + for (let i = newList.length - 1; i >= 0; i--) { + const entry = newList[i]; + if (entry.entryType !== 'navigation' && entry.entryType !== 'largest-contentful-paint') { + newEntries.push(entry); + continue; + } + + if (entry.entryType === 'navigation') { + const navigationEntry = entry as PerformanceNavigationTiming; + + if (entry.duration <= 0) { + // Ignore any navigation entries with duration 0, as they are likely duplicates + continue; + } + + // Check if the navigation entry is contained in currentList or newList + if ( + // Ensure new entry does not already exist in existing entries + !existingNavigationEntries.find(a => { + return a === navigationEntry || NAVIGATION_ENTRY_KEYS.every(key => a[key] === navigationEntry[key]); + }) && + // Ensure new entry does not already exist in new list of navigation entries + !newNavigationEntries.find(a => { + return a === navigationEntry || NAVIGATION_ENTRY_KEYS.every(key => a[key] === navigationEntry[key]); + }) + ) { + newNavigationEntries.push(navigationEntry); + } + + // Otherwise this navigation entry is considered a duplicate and is thrown away + continue; + } + + if (entry.entryType === 'largest-contentful-paint' && !foundNewLcp) { + // We want the latest LCP event only + if (!newLcpEntry || newLcpEntry.startTime < entry.startTime) { + newLcpEntry = entry; + foundNewLcp = true; + } + continue; + } + } + + return newEntries + .concat(newNavigationEntries, existingEntries, existingNavigationEntries, newLcpEntry ? [newLcpEntry] : []) + .sort((a, b) => a.startTime - b.startTime); +} + +exports.dedupePerformanceEntries = dedupePerformanceEntries; From a8cc69413017bb5dca94b4cd3c457b8a0282aaa9 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Wed, 21 Jun 2023 12:28:23 -0400 Subject: [PATCH 02/11] feat: send profiles in same envelope as txn --- package.json | 2 +- packages/browser/src/profiling/cache.ts | 72 ------ .../browser/src/profiling/hubextensions.ts | 220 ++++++------------ packages/browser/src/profiling/integration.ts | 102 ++------ .../browser/src/profiling/jsSelfProfiling.ts | 3 - packages/browser/src/profiling/utils.ts | 177 ++++---------- .../dedupePerformanceEntries.benchmark.ts | 47 ---- .../src/util/dedupePerformanceEntries.new.ts | 102 -------- 8 files changed, 144 insertions(+), 581 deletions(-) delete mode 100644 packages/browser/src/profiling/cache.ts delete mode 100644 packages/replay/src/util/dedupePerformanceEntries.benchmark.ts delete mode 100644 packages/replay/src/util/dedupePerformanceEntries.new.ts diff --git a/package.json b/package.json index 8ebd0541f657..4349b4196cc7 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "yalc:publish": "lerna run yalc:publish" }, "volta": { - "node": "16.19.0", + "node": "18.12.1", "yarn": "1.22.19" }, "workspaces": [ diff --git a/packages/browser/src/profiling/cache.ts b/packages/browser/src/profiling/cache.ts deleted file mode 100644 index ee62538e60cb..000000000000 --- a/packages/browser/src/profiling/cache.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Event } from '@sentry/types'; - -/** - * Creates a cache that evicts keys in fifo order - * @param size {Number} - */ -export function makeProfilingCache( - size: number, -): { - get: (key: Key) => Value | undefined; - add: (key: Key, value: Value) => void; - delete: (key: Key) => boolean; - clear: () => void; - size: () => number; -} { - // Maintain a fifo queue of keys, we cannot rely on Object.keys as the browser may not support it. - let evictionOrder: Key[] = []; - let cache: Record = {}; - - return { - add(key: Key, value: Value) { - while (evictionOrder.length >= size) { - // shift is O(n) but this is small size and only happens if we are - // exceeding the cache size so it should be fine. - const evictCandidate = evictionOrder.shift(); - - if (evictCandidate !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete cache[evictCandidate]; - } - } - - // in case we have a collision, delete the old key. - if (cache[key]) { - this.delete(key); - } - - evictionOrder.push(key); - cache[key] = value; - }, - clear() { - cache = {}; - evictionOrder = []; - }, - get(key: Key): Value | undefined { - return cache[key]; - }, - size() { - return evictionOrder.length; - }, - // Delete cache key and return true if it existed, false otherwise. - delete(key: Key): boolean { - if (!cache[key]) { - return false; - } - - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete cache[key]; - - for (let i = 0; i < evictionOrder.length; i++) { - if (evictionOrder[i] === key) { - evictionOrder.splice(i, 1); - break; - } - } - - return true; - }, - }; -} - -export const PROFILING_EVENT_CACHE = makeProfilingCache(20); diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index c1cd8d8a1165..292d4e672ae6 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -1,9 +1,10 @@ +/* eslint-disable complexity */ import { getCurrentHub } from '@sentry/core'; -import type { CustomSamplingContext, Transaction } from '@sentry/types'; +import type { Transaction } from '@sentry/types'; import { logger, uuid4 } from '@sentry/utils'; -import type { BrowserClient } from '../client'; import { WINDOW } from '../helpers'; +import { addToProfileQueue } from './integration'; import type { JSSelfProfile, JSSelfProfiler, @@ -17,120 +18,6 @@ export const MAX_PROFILE_DURATION_MS = 30_000; // once, it will always fail and this allows us to early return. let PROFILING_CONSTRUCTOR_FAILED = false; -// Takes a transaction and determines if it should be profiled or not. If it should be profiled, it returns the -// profile_id, otherwise returns undefined. Takes care of setting profile context on transaction as well -/** - * - */ -export function maybeProfileTransaction( - client: BrowserClient, - transaction: Transaction, - customSamplingContext?: CustomSamplingContext, -): string | undefined { - // profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. We dont perform - // the actual multiplication to get the final rate, but we discard the profile if the transaction was sampled, - // so anything after this block from here is based on the transaction sampling. - if (!transaction.sampled) { - return; - } - - // Client and options are required for profiling - if (!client) { - __DEBUG_BUILD__ && logger.log('[Profiling] Profiling disabled, no client found.'); - return; - } - - const options = client.getOptions(); - if (!options) { - __DEBUG_BUILD__ && logger.log('[Profiling] Profiling disabled, no options found.'); - return; - } - - // @ts-ignore profilesSampler is not part of the browser options yet - const profilesSampler = options.profilesSampler; - // @ts-ignore profilesSampleRate is not part of the browser options yet - let profilesSampleRate: number | boolean | undefined = options.profilesSampleRate; - - // Prefer sampler to sample rate if both are provided. - if (typeof profilesSampler === 'function') { - profilesSampleRate = profilesSampler({ transactionContext: transaction.toContext(), ...customSamplingContext }); - } - - // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The - // only valid values are booleans or numbers between 0 and 1.) - if (!isValidSampleRate(profilesSampleRate)) { - __DEBUG_BUILD__ && logger.warn('[Profiling] Discarding profile because of invalid sample rate.'); - return; - } - - // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped - if (!profilesSampleRate) { - __DEBUG_BUILD__ && - logger.log( - `[Profiling] Discarding profile because ${ - typeof profilesSampler === 'function' - ? 'profileSampler returned 0 or false' - : 'a negative sampling decision was inherited or profileSampleRate is set to 0' - }`, - ); - return; - } - - // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is - // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false. - const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate; - // Check if we should sample this profile - if (!sampled) { - __DEBUG_BUILD__ && - logger.log( - `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number( - profilesSampleRate, - )})`, - ); - return; - } - - const profile_id = uuid4(); - CpuProfilerBindings.startProfiling(profile_id); - - __DEBUG_BUILD__ && logger.log(`[Profiling] started profiling transaction: ${transaction.name}`); - - // set transaction context - do this regardless if profiling fails down the line - // so that we can still see the profile_id in the transaction context - return profile_id; -} - -/** - * - */ -export function stopTransactionProfile( - transaction: Transaction, - profile_id: string | undefined, -): ReturnType<(typeof CpuProfilerBindings)['stopProfiling']> | null { - // Should not happen, but satisfy the type checker and be safe regardless. - if (!profile_id) { - return null; - } - - const profile = CpuProfilerBindings.stopProfiling(profile_id); - - __DEBUG_BUILD__ && logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name}`); - - // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile. - if (!profile) { - __DEBUG_BUILD__ && - logger.log( - `[Profiling] profiler returned null profile for: ${transaction.name}`, - 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started', - ); - return null; - } - - // Assign profile_id to the profile - profile.profile_id = profile_id; - return profile; -} - /** * Check if profiler constructor is available. * @param maybeProfiler @@ -162,7 +49,7 @@ export function onProfilingStartRouteTransaction(transaction: Transaction | unde * startProfiling is called after the call to startTransaction in order to avoid our own code from * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction. */ -function wrapTransactionWithProfiling(transaction: Transaction): Transaction { +export function wrapTransactionWithProfiling(transaction: Transaction): Transaction { // Feature support check first const JSProfilerConstructor = WINDOW.Profiler; @@ -175,14 +62,6 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { return transaction; } - // profilesSampleRate is multiplied with tracesSampleRate to get the final sampling rate. - if (!transaction.sampled) { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Transaction is not sampled, skipping profiling'); - } - return transaction; - } - // If constructor failed once, it will always fail, so we can early return. if (PROFILING_CONSTRUCTOR_FAILED) { if (__DEBUG_BUILD__) { @@ -193,21 +72,52 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { const client = getCurrentHub().getClient(); const options = client && client.getOptions(); + if (!options) { + __DEBUG_BUILD__ && logger.log('[Profiling] Profiling disabled, no options found.'); + return transaction; + } - // @ts-ignore not part of the browser options yet - const profilesSampleRate = (options && options.profilesSampleRate) || 0; - if (profilesSampleRate === undefined) { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Profiling disabled, enable it by setting `profilesSampleRate` option to SDK init call.'); - } + // @ts-ignore profilesSampler is not part of the browser options yet + const profilesSampler = options.profilesSampler; + // @ts-ignore profilesSampleRate is not part of the browser options yet + let profilesSampleRate: number | boolean | undefined = options.profilesSampleRate; + + // Prefer sampler to sample rate if both are provided. + if (typeof profilesSampler === 'function') { + profilesSampleRate = profilesSampler({ transactionContext: transaction.toContext() }); + } + + // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The + // only valid values are booleans or numbers between 0 and 1.) + if (!isValidSampleRate(profilesSampleRate)) { + __DEBUG_BUILD__ && logger.warn('[Profiling] Discarding profile because of invalid sample rate.'); return transaction; } + // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped + if (!profilesSampleRate) { + __DEBUG_BUILD__ && + logger.log( + `[Profiling] Discarding profile because ${ + typeof profilesSampler === 'function' + ? 'profileSampler returned 0 or false' + : 'a negative sampling decision was inherited or profileSampleRate is set to 0' + }`, + ); + return transaction; + } + + // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is + // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false. + const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate; // Check if we should sample this profile - if (Math.random() > profilesSampleRate) { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Skip profiling transaction due to sampling.'); - } + if (!sampled) { + __DEBUG_BUILD__ && + logger.log( + `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number( + profilesSampleRate, + )})`, + ); return transaction; } @@ -254,19 +164,19 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { // event of an error or user mistake (calling transaction.finish multiple times), it is important that the behavior of onProfileHandler // is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler. // After the original finish method is called, the event will be reported through the integration and delegated to transport. - let processedProfile: ProcessedJSSelfProfile | null = null; + const processedProfile: ProcessedJSSelfProfile | null = null; /** * Idempotent handler for profile stop */ - function onProfileHandler(): void { + async function onProfileHandler(): Promise { // Check if the profile exists and return it the behavior has to be idempotent as users may call transaction.finish multiple times. if (!transaction) { - return; + return null; } // Satisfy the type checker, but profiler will always be defined here. if (!profiler) { - return; + return null; } if (processedProfile) { if (__DEBUG_BUILD__) { @@ -276,12 +186,12 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { 'already exists, returning early', ); } - return; + return null; } - profiler + return profiler .stop() - .then((p: JSSelfProfile): void => { + .then((p: JSSelfProfile): null => { if (maxDurationTimeoutID) { WINDOW.clearTimeout(maxDurationTimeoutID); maxDurationTimeoutID = undefined; @@ -299,16 +209,16 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started', ); } - return; + return null; } // If a profile has less than 2 samples, it is not useful and should be discarded. if (p.samples.length < 2) { - return; + return null; } - processedProfile = { ...p, profile_id: profileId }; - sendProfile(profileId, processedProfile); + addToProfileQueue({ ...p, profile_id: profileId }); + return null; }) .catch(error => { if (__DEBUG_BUILD__) { @@ -326,6 +236,7 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { transaction.name || transaction.description, ); } + // If the timeout exceeds, we want to stop profiling, but not finish the transaction void onProfileHandler(); }, MAX_PROFILE_DURATION_MS); @@ -337,18 +248,23 @@ function wrapTransactionWithProfiling(transaction: Transaction): Transaction { * startProfiling is called after the call to startTransaction in order to avoid our own code from * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction. */ - function profilingWrappedTransactionFinish(): Promise { + function profilingWrappedTransactionFinish(): Transaction { if (!transaction) { return originalFinish(); } // onProfileHandler should always return the same profile even if this is called multiple times. // Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared. - onProfileHandler(); - - // Set profile context - transaction.setContext('profile', { profile_id: profileId }); + void onProfileHandler() + .then(() => { + transaction.setContext('profile', { profile_id: profileId }); + originalFinish(); + }) + .catch(() => { + // If onProfileHandler fails, we still want to call the original finish method. + originalFinish(); + }); - return originalFinish(); + return transaction; } transaction.finish = profilingWrappedTransactionFinish; diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index cd79fc56916d..67b8affe1037 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -1,20 +1,23 @@ -import type { BrowserClient } from '@sentry/browser'; -import type { Event, EventProcessor, Hub, Integration, Transaction } from '@sentry/types'; +import type { EventProcessor, Hub, Integration, Transaction } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { PROFILING_EVENT_CACHE } from './cache'; -import { MAX_PROFILE_DURATION_MS, maybeProfileTransaction } from './hubextensions'; -import { addProfilesToEnvelope, findProfiledTransactionsFromEnvelope } from './utils'; +import type { BrowserClient } from './../client'; +import { wrapTransactionWithProfiling } from './hubextensions'; +import type { ProcessedJSSelfProfile, SentryProfile } from './jsSelfProfiling'; +import type { ProfiledEvent } from './utils'; +import { addProfilesToEnvelope, createProfilingEvent, findProfiledTransactionsFromEnvelope } from './utils'; -const MAX_PROFILE_QUEUE_LENGTH = 50; -const PROFILE_QUEUE: RawThreadCpuProfile[] = []; -const PROFILE_TIMEOUTS: Record = {}; +const MAX_PROFILE_QUEUE_SIZE = 50; +const PROFILE_QUEUE: ProcessedJSSelfProfile[] = []; -function addToProfileQueue(profile: RawThreadCpuProfile): void { +/** + * Adds the profile to the queue of profiles to be sent + */ +export function addToProfileQueue(profile: ProcessedJSSelfProfile): void { PROFILE_QUEUE.push(profile); // We only want to keep the last n profiles in the queue. - if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_LENGTH) { + if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_SIZE) { PROFILE_QUEUE.shift(); } } @@ -41,55 +44,12 @@ export class BrowserProfilingIntegration implements Integration { if (client && typeof client.on === 'function') { client.on('startTransaction', (transaction: Transaction) => { - const profile_id = maybeProfileTransaction(client, transaction, undefined); - - if (profile_id) { - const options = client.getOptions(); - // Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that - // currently exceed the default timeout set by the SDKs. - const maxProfileDurationMs = - (options._experiments && options._experiments['maxProfileDurationMs']) || MAX_PROFILE_DURATION_MS; - - // Enqueue a timeout to prevent profiles from running over max duration. - if (PROFILE_TIMEOUTS[profile_id]) { - global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); - delete PROFILE_TIMEOUTS[profile_id]; - } - - PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => { - __DEBUG_BUILD__ && - logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', transaction.name); - - const profile = stopTransactionProfile(transaction, profile_id); - if (profile) { - addToProfileQueue(profile); - } - }, maxProfileDurationMs); - - transaction.setContext('profile', { profile_id }); - // @ts-expect-error profile_id is not part of the metadata type - transaction.setMetadata({ profile_id: profile_id }); - } - }); - - client.on('finishTransaction', transaction => { - // @ts-expect-error profile_id is not part of the metadata type - const profile_id = transaction && transaction.metadata && transaction.metadata.profile_id; - if (profile_id) { - if (PROFILE_TIMEOUTS[profile_id]) { - global.clearTimeout(PROFILE_TIMEOUTS[profile_id]); - delete PROFILE_TIMEOUTS[profile_id]; - } - const profile = stopTransactionProfile(transaction, profile_id); - - if (profile) { - addToProfileQueue(profile); - } - } + wrapTransactionWithProfiling(transaction); }); client.on('beforeEnvelope', (envelope): void => { // if not profiles are in queue, there is nothing to add to the envelope. + if (!PROFILE_QUEUE.length) { return; } @@ -99,17 +59,21 @@ export class BrowserProfilingIntegration implements Integration { return; } - const profilesToAddToEnvelope: Profile[] = []; + const profilesToAddToEnvelope: SentryProfile[] = []; for (const profiledTransaction of profiledTransactionEvents) { - const profile_id = profiledTransaction?.contexts?.['profile']?.['profile_id']; + const profile_id = + profiledTransaction && + profiledTransaction.contexts && + profiledTransaction.contexts['profile'] && + profiledTransaction.contexts['profile']['profile_id']; if (!profile_id) { throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); } // Remove the profile from the transaction context before sending, relay will take care of the rest. - if (profiledTransaction?.contexts?.['.profile']) { + if (profiledTransaction && profiledTransaction.contexts && profiledTransaction.contexts['.profile']) { delete profiledTransaction.contexts.profile; } @@ -128,10 +92,10 @@ export class BrowserProfilingIntegration implements Integration { // Remove the profile from the queue. PROFILE_QUEUE.splice(profileIndex, 1); - const profile = createProfilingEvent(cpuProfile, profiledTransaction); + const profileEvent = createProfilingEvent(cpuProfile, profiledTransaction as ProfiledEvent); - if (profile) { - profilesToAddToEnvelope.push(profile); + if (profileEvent) { + profilesToAddToEnvelope.push(profileEvent); } } @@ -141,20 +105,4 @@ export class BrowserProfilingIntegration implements Integration { logger.warn('[Profiling] Client does not support hooks, profiling will be disabled'); } } - - /** - * @inheritDoc - */ - public handleGlobalEvent(event: Event): Event { - const profileId = event.contexts && event.contexts['profile'] && event.contexts['profile']['profile_id']; - - if (profileId && typeof profileId === 'string') { - if (__DEBUG_BUILD__) { - logger.log('[Profiling] Profiling event found, caching it.'); - } - PROFILING_EVENT_CACHE.add(profileId, event); - } - - return event; - } } diff --git a/packages/browser/src/profiling/jsSelfProfiling.ts b/packages/browser/src/profiling/jsSelfProfiling.ts index 778342ba0193..60b10c9896ab 100644 --- a/packages/browser/src/profiling/jsSelfProfiling.ts +++ b/packages/browser/src/profiling/jsSelfProfiling.ts @@ -51,9 +51,6 @@ declare global { } } -export interface RawThreadCpuProfile extends JSSelfProfile { - profile_id: string; -} export interface ThreadCpuProfile { samples: { stack_id: number; diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 751e7a92948a..4b327bef87e8 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -1,36 +1,14 @@ /* eslint-disable max-lines */ import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core'; -import type { - DebugImage, - DsnComponents, - DynamicSamplingContext, - Envelope, - Event, - EventEnvelope, - EventEnvelopeHeaders, - EventItem, - SdkInfo, - SdkMetadata, - StackFrame, - StackParser, -} from '@sentry/types'; -import { - createEnvelope, - dropUndefinedKeys, - dsnToString, - forEachEnvelopeItem, - GLOBAL_OBJ, - logger, - uuid4, -} from '@sentry/utils'; +import type { DebugImage, Envelope, Event, StackFrame, StackParser } from '@sentry/types'; +import { forEachEnvelopeItem, GLOBAL_OBJ, logger, uuid4 } from '@sentry/utils'; import { WINDOW } from '../helpers'; import type { JSSelfProfile, JSSelfProfileStack, ProcessedJSSelfProfile, - RawThreadCpuProfile, SentryProfile, ThreadCpuProfile, } from './jsSelfProfiling'; @@ -42,9 +20,9 @@ const THREAD_ID_STRING = String(0); const THREAD_NAME = 'main'; // Machine properties (eval only once) -let OS_PLATFORM = ''; // macos -let OS_PLATFORM_VERSION = ''; // 13.2 -let OS_ARCH = ''; // arm64 +let OS_PLATFORM = ''; +let OS_PLATFORM_VERSION = ''; +let OS_ARCH = ''; let OS_BROWSER = (WINDOW.navigator && WINDOW.navigator.userAgent) || ''; let OS_MODEL = ''; const OS_LOCALE = @@ -91,7 +69,9 @@ if (isUserAgentData(userAgentData)) { .catch(e => void e); } -function isRawThreadCpuProfile(profile: ThreadCpuProfile | RawThreadCpuProfile): profile is RawThreadCpuProfile { +function isProcessedJSSelfProfile( + profile: ThreadCpuProfile | ProcessedJSSelfProfile, +): profile is ProcessedJSSelfProfile { return !('thread_metadata' in profile); } @@ -100,8 +80,8 @@ function isRawThreadCpuProfile(profile: ThreadCpuProfile | RawThreadCpuProfile): /** * */ -export function enrichWithThreadInformation(profile: ThreadCpuProfile | RawThreadCpuProfile): ThreadCpuProfile { - if (!isRawThreadCpuProfile(profile)) { +export function enrichWithThreadInformation(profile: ThreadCpuProfile | ProcessedJSSelfProfile): ThreadCpuProfile { + if (!isProcessedJSSelfProfile(profile)) { return profile; } @@ -112,52 +92,7 @@ export function enrichWithThreadInformation(profile: ThreadCpuProfile | RawThrea // by the integration before the event is processed by other integrations. export interface ProfiledEvent extends Event { sdkProcessingMetadata: { - profile?: RawThreadCpuProfile; - }; -} - -/** Extract sdk info from from the API metadata */ -function getSdkMetadataForEnvelopeHeader(metadata?: SdkMetadata): SdkInfo | undefined { - if (!metadata || !metadata.sdk) { - return undefined; - } - - return { name: metadata.sdk.name, version: metadata.sdk.version } as SdkInfo; -} - -/** - * Apply SdkInfo (name, version, packages, integrations) to the corresponding event key. - * Merge with existing data if any. - **/ -function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event { - if (!sdkInfo) { - return event; - } - event.sdk = event.sdk || {}; - event.sdk.name = event.sdk.name || sdkInfo.name || 'unknown sdk'; - event.sdk.version = event.sdk.version || sdkInfo.version || 'unknown sdk version'; - event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])]; - event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])]; - return event; -} - -function createEventEnvelopeHeaders( - event: Event, - sdkInfo: SdkInfo | undefined, - tunnel: string | undefined, - dsn: DsnComponents, -): EventEnvelopeHeaders { - const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata['dynamicSamplingContext']; - - return { - event_id: event.event_id as string, - sent_at: new Date().toISOString(), - ...(sdkInfo && { sdk: sdkInfo }), - ...(!!tunnel && { dsn: dsnToString(dsn) }), - ...(event.type === 'transaction' && - dynamicSamplingContext && { - trace: dropUndefinedKeys({ ...dynamicSamplingContext }) as DynamicSamplingContext, - }), + profile?: ProcessedJSSelfProfile; }; } @@ -190,50 +125,30 @@ function getTraceId(event: Event): string { /** * Creates a profiling event envelope from a Sentry event. */ -export function createProfilingEventEnvelope( - event: ProfiledEvent, - dsn: DsnComponents, - metadata?: SdkMetadata, - tunnel?: string, -): EventEnvelope | null { +export function createProfilePayload(event: ProfiledEvent, processedProfile: ProcessedJSSelfProfile): SentryProfile { if (event.type !== 'transaction') { // createProfilingEventEnvelope should only be called for transactions, // we type guard this behavior with isProfiledTransactionEvent. throw new TypeError('Profiling events may only be attached to transactions, this should never occur.'); } - const rawProfile = event.sdkProcessingMetadata['profile'] as ProcessedJSSelfProfile | null; - - if (rawProfile === undefined || rawProfile === null) { + if (processedProfile === undefined || processedProfile === null) { throw new TypeError( - `Cannot construct profiling event envelope without a valid profile. Got ${rawProfile} instead.`, + `Cannot construct profiling event envelope without a valid profile. Got ${processedProfile} instead.`, ); } - if (!rawProfile.profile_id) { + if (!processedProfile.profile_id) { throw new TypeError('Profile is missing profile_id'); } - if (rawProfile.samples.length <= 1) { - if (__DEBUG_BUILD__) { - // Log a warning if the profile has less than 2 samples so users can know why - // they are not seeing any profiling data and we cant avoid the back and forth - // of asking them to provide us with a dump of the profile data. - logger.log('[Profiling] Discarding profile because it contains less than 2 samples'); - } - return null; - } - const traceId = getTraceId(event); - const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata); - enhanceEventWithSdkInfo(event, metadata && metadata.sdk); - const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn); - const enrichedThreadProfile = enrichWithThreadInformation(rawProfile); + const enrichedThreadProfile = enrichWithThreadInformation(processedProfile); const transactionStartMs = typeof event.start_timestamp === 'number' ? event.start_timestamp * 1000 : Date.now(); const transactionEndMs = typeof event.timestamp === 'number' ? event.timestamp * 1000 : Date.now(); const profile: SentryProfile = { - event_id: rawProfile.profile_id, + event_id: processedProfile.profile_id, timestamp: new Date(transactionStartMs).toISOString(), platform: 'javascript', version: '1', @@ -256,7 +171,7 @@ export function createProfilingEventEnvelope( is_emulator: false, }, debug_meta: { - images: applyDebugMetadata(rawProfile.resources), + images: applyDebugMetadata(processedProfile.resources), }, profile: enrichedThreadProfile, transactions: [ @@ -271,15 +186,7 @@ export function createProfilingEventEnvelope( ], }; - const envelopeItem: EventItem = [ - { - type: 'profile', - }, - // @ts-ignore this is missing in typedef - profile, - ]; - - return createEnvelope(envelopeHeaders, [envelopeItem]); + return profile; } /** @@ -289,21 +196,6 @@ export function isProfiledTransactionEvent(event: Event): event is ProfiledEvent return !!(event.sdkProcessingMetadata && event.sdkProcessingMetadata['profile']); } -// Due to how profiles are attached to event metadata, we may sometimes want to remove them to ensure -// they are not processed by other Sentry integrations. This can be the case when we cannot construct a valid -// profile from the data we have or some of the mechanisms to send the event (Hub, Transport etc) are not available to us. -/** - * - */ -export function maybeRemoveProfileFromSdkMetadata(event: Event | ProfiledEvent): Event { - if (!isProfiledTransactionEvent(event)) { - return event; - } - - delete event.sdkProcessingMetadata.profile; - return event; -} - /** * Converts a JSSelfProfile to a our sampled format. * Does not currently perform stack indexing. @@ -532,3 +424,34 @@ export function isValidSampleRate(rate: unknown): boolean { } return true; } + +function isValidProfile(profile: ProcessedJSSelfProfile): profile is ProcessedJSSelfProfile & { profile_id: string } { + if (profile.samples.length <= 1) { + if (__DEBUG_BUILD__) { + // Log a warning if the profile has less than 2 samples so users can know why + // they are not seeing any profiling data and we cant avoid the back and forth + // of asking them to provide us with a dump of the profile data. + logger.log('[Profiling] Discarding profile because it contains less than 2 samples'); + } + return false; + } + + if (!profile.profile_id) { + return false; + } + + return true; +} + +/** + * Creates a profiling envelope item, if the profile does not pass validation, returns null. + * @param event + * @returns {Profile | null} + */ +export function createProfilingEvent(profile: ProcessedJSSelfProfile, event: ProfiledEvent): SentryProfile | null { + if (!isValidProfile(profile)) { + return null; + } + + return createProfilePayload(event, profile); +} diff --git a/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts b/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts deleted file mode 100644 index 279cb720cfb4..000000000000 --- a/packages/replay/src/util/dedupePerformanceEntries.benchmark.ts +++ /dev/null @@ -1,47 +0,0 @@ -const Benchmark = require('benchmark'); - -const fns = require('./dedupePerformanceEntries'); -const fnsNew = require('./dedupePerformanceEntries.new'); - -const data = JSON.parse( - '[{"name":"https://sentry.sentry.io/issues/","entryType":"navigation","startTime":0,"duration":1316.5,"initiatorType":"navigation","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":464.7000000476837,"domainLookupStart":466.89999997615814,"domainLookupEnd":498.2000000476837,"connectStart":498.2000000476837,"secureConnectionStart":530.6000000238419,"connectEnd":602.3999999761581,"requestStart":602.3999999761581,"responseStart":951.3000000715256,"responseEnd":957.3000000715256,"transferSize":7984,"encodedBodySize":7684,"decodedBodySize":19215,"responseStatus":200,"serverTiming":[],"unloadEventStart":0,"unloadEventEnd":0,"domInteractive":1056.3999999761581,"domContentLoadedEventStart":1056.3999999761581,"domContentLoadedEventEnd":1056.5,"domComplete":1315.1000000238419,"loadEventStart":1315.1000000238419,"loadEventEnd":1316.5,"type":"navigate","redirectCount":0,"activationStart":0},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/entrypoints-hashed/sentry-TynoRHRu_eq0ob3bedRRtUK-KuSAERzrmgEKRbzAXCI.css","entryType":"resource","startTime":963.8999999761581,"duration":20.700000047683716,"initiatorType":"link","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":963.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":984.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://js.sentry-cdn.com/c51734c603c4430eb57cb0a5728a479d.min.js","entryType":"resource","startTime":964.1000000238419,"duration":23.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":987.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/entrypoints-hashed/app-CcPyCILeKKdnKtN3PEQ8pUuiC3YiM7FoxWRZaq-pv8A.js","entryType":"resource","startTime":964.2000000476837,"duration":24.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":988.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/541fb42adc5d8c91924cc9755b0ae223/sentry/js/ads.js","entryType":"resource","startTime":964.3000000715256,"duration":23.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":987.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://js.stripe.com/v3/","entryType":"resource","startTime":964.3999999761581,"duration":322.7000000476837,"initiatorType":"script","nextHopProtocol":"h2","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.3999999761581,"domainLookupStart":1033.8999999761581,"domainLookupEnd":1045.2000000476837,"connectStart":1045.2000000476837,"secureConnectionStart":1122.2000000476837,"connectEnd":1203.3000000715256,"requestStart":1203.5,"responseStart":1284.1000000238419,"responseEnd":1287.1000000238419,"transferSize":300,"encodedBodySize":127071,"decodedBodySize":474393,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/541fb42adc5d8c91924cc9755b0ae223/sentry/images/sentry-loader.svg","entryType":"resource","startTime":964.5,"duration":75.70000004768372,"initiatorType":"img","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1040.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://static.zdassets.com/ekr/snippet.js?key=99cb65b7-1616-43cd-b8be-203f91ec932e","entryType":"resource","startTime":964.5,"duration":68.80000007152557,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":964.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1033.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"head-start","entryType":"mark","startTime":1030.3000000715256,"duration":0},{"name":"app.page.body-load","entryType":"measure","startTime":1030.3000000715256,"duration":25.5},{"name":"app.page.bundle-load","entryType":"measure","startTime":1030.3000000715256,"duration":217.69999992847443},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_bootstrap_index_tsx.b99c6b7cd539e00e7e04.js","entryType":"resource","startTime":1033.6000000238419,"duration":7.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1041.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_sentry_core_esm_exports_js-node_modules_moment_moment_js.2a74ee6a7220aedbc010.js","entryType":"resource","startTime":1033.8000000715256,"duration":8.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1042,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_emotion_react_jsx-runtime_dist_emotion-react-jsx-runtime_browser_esm_js--5816c0.e8a1be9dbda970aac154.js","entryType":"resource","startTime":1033.8999999761581,"duration":12.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1033.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1046.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/node_modules_emotion_unitless_dist_emotion-unitless_esm_js-app_bootstrap_initializeLocale_tsx-a2b5a60.3df912d3033a82371b06.js","entryType":"resource","startTime":1034.1000000238419,"duration":11.899999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1034.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1046,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://reload.getsentry.net/page/","entryType":"resource","startTime":1037.7000000476837,"duration":174.29999995231628,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1037.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1212,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"head-end","entryType":"mark","startTime":1038.3999999761581,"duration":0},{"name":"https://cdn.pendo.io/agent/static/dc5c6fad-c3ae-4441-49ce-0ae37103aed7/pendo.js","entryType":"resource","startTime":1038.3999999761581,"duration":103.80000007152557,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1038.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1142.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://ekr.zdassets.com/compose/99cb65b7-1616-43cd-b8be-203f91ec932e","entryType":"resource","startTime":1055.7000000476837,"duration":127.39999997615814,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1055.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1183.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"body-end","entryType":"mark","startTime":1055.8000000715256,"duration":0},{"name":"first-paint","entryType":"paint","startTime":1058.8000000715256,"duration":0},{"name":"first-contentful-paint","entryType":"paint","startTime":1058.8000000715256,"duration":0},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_internationalized_string_dist_module_js-node_modules_react-aria_focus_di-544641.75d2e5586f6ac68b95f3.js","entryType":"resource","startTime":1062,"duration":4.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1066.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_button_dist_module_js-node_modules_react-aria_gridlist_dist_m-81a0c4.bb623b51efc44bf23e31.js","entryType":"resource","startTime":1062.2000000476837,"duration":6.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1068.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_radio_dist_module_js-node_modules_react-stately_list_dist_mod-7bc4f5.ce5e7d7fd11388c76110.js","entryType":"resource","startTime":1062.5,"duration":4.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1067.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_selection_dist_module_js-node_modules_lodash_cloneDeep_js-nod-d70379.d688b691982475d8de57.js","entryType":"resource","startTime":1062.8999999761581,"duration":7,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1062.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1069.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_date-fns_format_index_js.ecf7cb2fa6edc821f4eb.js","entryType":"resource","startTime":1063.1000000238419,"duration":5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1068.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_buffer_index_js.dcacea8575981fed3f5c.js","entryType":"resource","startTime":1063.3000000715256,"duration":5.899999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1069.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_echarts-for-react_lib_core_js-node_modules_echarts_core_js-node_modules_-be3c62.ed7280025d8653d5ad4a.js","entryType":"resource","startTime":1063.5,"duration":24.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1087.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_moment-timezone_index_js-node_modules_reflux_src_index_js.9cc3b3392494216b18c8.js","entryType":"resource","startTime":1063.6000000238419,"duration":22.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1086.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_emotion_is-prop-valid_dist_is-prop-valid_browser_esm_js-node_modules_emo-f27c2a.c8a681442de6bd91116a.js","entryType":"resource","startTime":1063.7000000476837,"duration":27.299999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1091,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_core-js_modules_es_array_at_js-node_modules_core-js_modules_es_string_at-ac4873.6673d5eace8f33b8e81b.js","entryType":"resource","startTime":1063.8000000715256,"duration":7.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1071.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_js-beautify_js_index_js.0a8d3ce59a51dc11f6fd.js","entryType":"resource","startTime":1063.8000000715256,"duration":14,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1077.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_focus-trap_dist_focus-trap_esm_js-node_modules_history_lib_createMemoryH-9f114c.82628466bbc3011927af.js","entryType":"resource","startTime":1063.8999999761581,"duration":14.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1063.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-node_modules_react-aria_interactions_dist_module_js-node_modules_react-aria_tabs_dist-0dd1d1.da9ab3a97943160dcae2.js","entryType":"resource","startTime":1064.1000000238419,"duration":12.5,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.6000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-getsentry_node_modules_lodash__baseClone_js-getsentry_node_modules_lodash__baseIsEqua-18ca52.1a0051e2efb17920535c.js","entryType":"resource","startTime":1064.3000000715256,"duration":10.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1074.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/vendors-getsentry_node_modules_amplitude_analytics-browser_lib_esm_index_js-getsentry_node_mo-2275d3.394f54209591e3d776cb.js","entryType":"resource","startTime":1064.3999999761581,"duration":12.400000095367432,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_featureFeedback_feedbackModal_tsx.cfb6a3f320673aceab2f.js","entryType":"resource","startTime":1064.5,"duration":10.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1075.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_tag_tsx-app_components_textOverflow_tsx-app_components_timeSince_tsx-app_utils-b137eb.f41224d66223c5b07f08.js","entryType":"resource","startTime":1064.8999999761581,"duration":10.800000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1064.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1075.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_acl_feature_tsx-app_components_assistant_guideAnchor_tsx-app_components_clipbo-a9c4fa.b0ca9f703fe2c2389484.js","entryType":"resource","startTime":1065,"duration":13.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_events_interfaces_frame_utils_tsx-app_components_segmentedControl_tsx.036777a408266806394e.js","entryType":"resource","startTime":1065.2000000476837,"duration":10.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_asyncComponent_tsx.4e8e1eece3af7680e5b0.js","entryType":"resource","startTime":1065.3999999761581,"duration":10.900000095367432,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1076.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_guides_tsx-app_components_charts_utils_tsx-app_utils_discover_fieldRendere-6404c2.57f08970b136b7ab5492.js","entryType":"resource","startTime":1065.3999999761581,"duration":23.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_searchSyntax_parser_tsx-app_components_searchSyntax_utils_tsx-app_utils_withPa-338bbb.30bc1943da4795e58e13.js","entryType":"resource","startTime":1065.7000000476837,"duration":12,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1077.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_navigation_tsx-app_components_pagination_tsx-app_utils_useRouter_tsx-app_u-e3e402.a492cd094ceb8cebdc8b.js","entryType":"resource","startTime":1065.8000000715256,"duration":12.399999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1078.2000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_alerts_toastIndicator_tsx-app_components_assistant_getGuidesContent_tsx-app_co-3627de.49c1887d6fb6ab1bf842.js","entryType":"resource","startTime":1065.8999999761581,"duration":22.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1065.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1088.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_assigneeSelector_tsx-app_components_charts_barChart_tsx-app_components_organiz-1c0127.5cc379e8a2e3c80c8d3d.js","entryType":"resource","startTime":1066,"duration":17.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1083.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_eventOrGroupHeader_tsx-app_utils_useParams_tsx.0ae6c7e8cf1492bfec03.js","entryType":"resource","startTime":1066.3999999761581,"duration":13.100000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1079.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_deprecatedforms_selectField_tsx-app_components_forms_apiForm_tsx-app_component-fe94fb.91f9f63fa45bc4790767.js","entryType":"resource","startTime":1066.3999999761581,"duration":18.40000009536743,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1084.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_onboarding_documentationWrapper_tsx-app_components_onboarding_footer_tsx-app_v-7ba665.54c11556cadee8f8e5cc.js","entryType":"resource","startTime":1066.5,"duration":13.600000023841858,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.1000000238419,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_actions_button_tsx-app_components_dropdownAutoComplete_autoCompleteFilter_tsx--91f7e8.77d1c40bdaa6bb9db5d4.js","entryType":"resource","startTime":1066.7000000476837,"duration":14,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_components_banner_tsx-app_components_charts_optionSelector_tsx.bc62abd018a73f0f63f7.js","entryType":"resource","startTime":1066.7000000476837,"duration":14.199999928474426,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1066.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1080.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_actionCreators_account_tsx-app_actionCreators_discoverSavedQueries_tsx-app_components_eve-338d0c.cada450527f8ceb0f451.js","entryType":"resource","startTime":1067.1000000238419,"duration":22.799999952316284,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089.8999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_views_onboarding_setupDocs_tsx.e47b4c53ba4bbfa504e7.js","entryType":"resource","startTime":1067.3000000715256,"duration":14.099999904632568,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1081.3999999761581,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_views_organizationStats_usageStatsOrg_tsx.d3d29fe5626988e28762.js","entryType":"resource","startTime":1067.3999999761581,"duration":14.300000071525574,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1081.7000000476837,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/getsentry_static_getsentry_gsApp_registerHooks_tsx-app_components_codeSnippet_tsx-getsentry_s-61ae35.7e6a34df145cbecac219.js","entryType":"resource","startTime":1067.3999999761581,"duration":19.90000009536743,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.3999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1087.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/getsentry_static_getsentry_gsApp_initializeBundleMetrics_tsx.9fe7b144490c0ce9c4c6.js","entryType":"resource","startTime":1067.6000000238419,"duration":14.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1082.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/node_modules_emotion_unitless_dist_emotion-unitless_esm_js-app_bootstrap_initializeMain_tsx-n-f02164.cb9d501fc041dc503ed2.js","entryType":"resource","startTime":1067.6000000238419,"duration":21.700000047683716,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1067.6000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1089.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_plugins_index_tsx.929e41736b0670e5e0ba.js","entryType":"resource","startTime":1226.1000000238419,"duration":1.399999976158142,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1226.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1227.5,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://s1.sentry-cdn.com/_static/dist/sentry/chunks/app_bootstrap_initializeApp_tsx.1cbf31514ffcc3a3528f.js","entryType":"resource","startTime":1226.2000000476837,"duration":1.7999999523162842,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1226.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1228,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"sentry-tracing-init","entryType":"mark","startTime":1243,"duration":0},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":1247.8999999761581,"duration":74.10000002384186,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1247.8999999761581,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1322,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"sentry-app-init","entryType":"mark","startTime":1248,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/?detailed=0","entryType":"resource","startTime":1259.7000000476837,"duration":262.2999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1259.7000000476837,"domainLookupStart":1259.7000000476837,"domainLookupEnd":1259.7000000476837,"connectStart":1259.7000000476837,"secureConnectionStart":1259.7000000476837,"connectEnd":1259.7000000476837,"requestStart":1260.8999999761581,"responseStart":1520.7000000476837,"responseEnd":1522,"transferSize":4982,"encodedBodySize":4682,"decodedBodySize":15837,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/projects/?all_projects=1&collapse=latestDeploys","entryType":"resource","startTime":1260.1000000238419,"duration":611.7999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1260.1000000238419,"domainLookupStart":1262.3999999761581,"domainLookupEnd":1262.3999999761581,"connectStart":1262.3999999761581,"secureConnectionStart":1293.6000000238419,"connectEnd":1332.6000000238419,"requestStart":1333,"responseStart":1870.7000000476837,"responseEnd":1871.8999999761581,"transferSize":23383,"encodedBodySize":23083,"decodedBodySize":148289,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/teams/","entryType":"resource","startTime":1260.3000000715256,"duration":547.6999999284744,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1260.3000000715256,"domainLookupStart":1261.2000000476837,"domainLookupEnd":1261.2000000476837,"connectStart":1261.2000000476837,"secureConnectionStart":1294,"connectEnd":1332.8000000715256,"requestStart":1333.1000000238419,"responseStart":1806.7000000476837,"responseEnd":1808,"transferSize":18228,"encodedBodySize":17928,"decodedBodySize":205169,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/?member=1","entryType":"resource","startTime":1261.3999999761581,"duration":371.40000009536743,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1261.3999999761581,"domainLookupStart":1263.2000000476837,"domainLookupEnd":1263.2000000476837,"connectStart":1263.2000000476837,"secureConnectionStart":1295.3000000715256,"connectEnd":1332.8999999761581,"requestStart":1333.2000000476837,"responseStart":1631.8000000715256,"responseEnd":1632.8000000715256,"transferSize":3486,"encodedBodySize":3186,"decodedBodySize":28039,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/internal/health/","entryType":"resource","startTime":1261.6000000238419,"duration":128.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1261.6000000238419,"domainLookupStart":1262.2000000476837,"domainLookupEnd":1262.3000000715256,"connectStart":1262.3000000715256,"secureConnectionStart":1295.3999999761581,"connectEnd":1332.8999999761581,"requestStart":1333.2000000476837,"responseStart":1388.7000000476837,"responseEnd":1390,"transferSize":300,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/assistant/","entryType":"resource","startTime":1262.5,"duration":158.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1262.5,"domainLookupStart":1262.8999999761581,"domainLookupEnd":1262.8999999761581,"connectStart":1262.8999999761581,"secureConnectionStart":1295,"connectEnd":1332.8999999761581,"requestStart":1333.3000000715256,"responseStart":1419.2000000476837,"responseEnd":1420.8999999761581,"transferSize":1009,"encodedBodySize":709,"decodedBodySize":709,"responseStatus":200,"serverTiming":[]},{"name":"@grammarly-extension:checkScriptInitStart","entryType":"mark","startTime":1302.8000000715256,"duration":0},{"name":"@grammarly-extension:checkScriptInitEnd","entryType":"mark","startTime":1303.8000000715256,"duration":0},{"name":"https://js.stripe.com/v3/m-outer-93afeeb17bc37e711759584dbfc50d47.html#url=https%3A%2F%2Fsentry.sentry.io%2Fissues%2F&title=Sentry&referrer=&muid=78f25c63-c9aa-49b3-b21e-d24aa1a9e8a524eeea&sid=NA&version=6&preview=false","entryType":"resource","startTime":1316.5,"duration":5.899999976158142,"initiatorType":"iframe","nextHopProtocol":"h2","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1316.5,"domainLookupStart":1316.5,"domainLookupEnd":1316.5,"connectStart":1316.5,"secureConnectionStart":1316.5,"connectEnd":1316.5,"requestStart":1319.3000000715256,"responseStart":1319.7000000476837,"responseEnd":1322.3999999761581,"transferSize":0,"encodedBodySize":122,"decodedBodySize":200,"responseStatus":0,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/client-state/","entryType":"resource","startTime":1523.8000000715256,"duration":247.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1523.8000000715256,"domainLookupStart":1523.8000000715256,"domainLookupEnd":1523.8000000715256,"connectStart":1523.8000000715256,"secureConnectionStart":1523.8000000715256,"connectEnd":1523.8000000715256,"requestStart":1524.6000000238419,"responseStart":1770.5,"responseEnd":1771.3000000715256,"transferSize":302,"encodedBodySize":2,"decodedBodySize":2,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/subscriptions/sentry/","entryType":"resource","startTime":1538.5,"duration":238.20000004768372,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1538.5,"domainLookupStart":1538.5,"domainLookupEnd":1538.5,"connectStart":1538.5,"secureConnectionStart":1538.5,"connectEnd":1538.5,"requestStart":1541.8999999761581,"responseStart":1775.3000000715256,"responseEnd":1776.7000000476837,"transferSize":2094,"encodedBodySize":1794,"decodedBodySize":5762,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/broadcasts/","entryType":"resource","startTime":1538.8999999761581,"duration":238.20000004768372,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1538.8999999761581,"domainLookupStart":1538.8999999761581,"domainLookupEnd":1538.8999999761581,"connectStart":1538.8999999761581,"secureConnectionStart":1538.8999999761581,"connectEnd":1538.8999999761581,"requestStart":1633,"responseStart":1775.3000000715256,"responseEnd":1777.1000000238419,"transferSize":1105,"encodedBodySize":805,"decodedBodySize":805,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/promotions/trigger-check/","entryType":"resource","startTime":1540.7000000476837,"duration":139.79999995231628,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1540.7000000476837,"domainLookupStart":1540.7000000476837,"domainLookupEnd":1540.7000000476837,"connectStart":1540.7000000476837,"secureConnectionStart":1540.7000000476837,"connectEnd":1540.7000000476837,"requestStart":1542.2000000476837,"responseStart":1680,"responseEnd":1680.5,"transferSize":398,"encodedBodySize":98,"decodedBodySize":98,"responseStatus":200,"serverTiming":[]},{"name":"https://t687h3m0nh65.statuspage.io/api/v2/incidents/unresolved.json","entryType":"resource","startTime":1541.1000000238419,"duration":130.89999997615814,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1541.1000000238419,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1672,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/tags/?statsPeriod=14d&use_cache=1","entryType":"resource","startTime":1544.3000000715256,"duration":1457.3999999761581,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.3000000715256,"domainLookupStart":1544.3000000715256,"domainLookupEnd":1544.3000000715256,"connectStart":1544.3000000715256,"secureConnectionStart":1544.3000000715256,"connectEnd":1544.3000000715256,"requestStart":1680.8000000715256,"responseStart":2996.100000023842,"responseEnd":3001.7000000476837,"transferSize":7419,"encodedBodySize":7119,"decodedBodySize":25782,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/users/","entryType":"resource","startTime":1544.6000000238419,"duration":2509.7999999523163,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.6000000238419,"domainLookupStart":1544.6000000238419,"domainLookupEnd":1544.6000000238419,"connectStart":1544.6000000238419,"secureConnectionStart":1544.6000000238419,"connectEnd":1544.6000000238419,"requestStart":1771.7000000476837,"responseStart":3961.8000000715256,"responseEnd":4054.399999976158,"transferSize":45219,"encodedBodySize":44919,"decodedBodySize":261770,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/searches/","entryType":"resource","startTime":1544.8999999761581,"duration":374.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1544.8999999761581,"domainLookupStart":1544.8999999761581,"domainLookupEnd":1544.8999999761581,"connectStart":1544.8999999761581,"secureConnectionStart":1544.8999999761581,"connectEnd":1544.8999999761581,"requestStart":1777.3999999761581,"responseStart":1918.6000000238419,"responseEnd":1919.3999999761581,"transferSize":1445,"encodedBodySize":1145,"decodedBodySize":4268,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/organization-avatar/24f6f762f7a7473888b259c566da5adb/?s=120","entryType":"resource","startTime":1545.3000000715256,"duration":290.7999999523163,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1545.3000000715256,"domainLookupStart":1545.3000000715256,"domainLookupEnd":1545.3000000715256,"connectStart":1545.3000000715256,"secureConnectionStart":1545.3000000715256,"connectEnd":1545.3000000715256,"requestStart":1777.5,"responseStart":1835.3999999761581,"responseEnd":1836.1000000238419,"transferSize":6603,"encodedBodySize":6303,"decodedBodySize":6303,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":1600,"duration":38.300000071525574,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1600,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":1638.3000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/prompts-activity/?feature=deactivated_member_alert&feature=errors_overage_alert&feature=attachments_overage_alert&feature=transactions_overage_alert&feature=replays_overage_alert&feature=errors_warning_alert&feature=attachments_warning_alert&feature=transactions_warning_alert&feature=replays_warning_alert&organization_id=1","entryType":"resource","startTime":1780.8999999761581,"duration":91.60000002384186,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1780.8999999761581,"domainLookupStart":1780.8999999761581,"domainLookupEnd":1780.8999999761581,"connectStart":1780.8999999761581,"secureConnectionStart":1780.8999999761581,"connectEnd":1780.8999999761581,"requestStart":1808.3000000715256,"responseStart":1872.2000000476837,"responseEnd":1872.5,"transferSize":315,"encodedBodySize":15,"decodedBodySize":15,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/pendo-details/","entryType":"resource","startTime":1791.5,"duration":219.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1791.5,"domainLookupStart":1791.5,"domainLookupEnd":1791.5,"connectStart":1791.5,"secureConnectionStart":1791.5,"connectEnd":1791.5,"requestStart":1836.3999999761581,"responseStart":2010.2000000476837,"responseEnd":2011,"transferSize":1092,"encodedBodySize":792,"decodedBodySize":792,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/processingissues/","entryType":"resource","startTime":1962.7000000476837,"duration":235.39999997615814,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1962.7000000476837,"domainLookupStart":1962.7000000476837,"domainLookupEnd":1962.7000000476837,"connectStart":1962.7000000476837,"secureConnectionStart":1962.7000000476837,"connectEnd":1962.7000000476837,"requestStart":1963.7000000476837,"responseStart":2196.5,"responseEnd":2198.100000023842,"transferSize":676,"encodedBodySize":376,"decodedBodySize":3988,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues/?collapse=stats&expand=owners&expand=inbox&limit=25&query=is%3Aunresolved&shortIdLookup=1&statsPeriod=14d","entryType":"resource","startTime":1963.3000000715256,"duration":314.5,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1963.3000000715256,"domainLookupStart":1963.3000000715256,"domainLookupEnd":1963.3000000715256,"connectStart":1963.3000000715256,"secureConnectionStart":1963.3000000715256,"connectEnd":1963.3000000715256,"requestStart":1963.8999999761581,"responseStart":2275.7000000476837,"responseEnd":2277.8000000715256,"transferSize":5092,"encodedBodySize":4792,"decodedBodySize":27778,"responseStatus":200,"serverTiming":[]},{"name":"https://data.pendo.io/data/ptm.gif/dc5c6fad-c3ae-4441-49ce-0ae37103aed7?v=2.181.0_prod&ct=1682429095854&jzb=eJwtj81qwzAQhN9lz8a2_KPKPpdAoJAWUnooJcixQmQcS6xWLibk3bN1rYtWMzv6mO870OINtDA63UMCHbrfYPBE9saqkKqoiiZvalU1Ccw2WHJ4sj0HRCpkpQpZckqfzy5OtBksRBx5uhL50GZZMBPhkm6XdZkNIZqQ8aJH5wO095W_UVWdp2WznhcpaiUevKeR00fd7RkxxXFMgP4fcCx28eNd7N6G2WtzqPjXC-qbWc3XfP4U-dD1l3L_Ja7yr-JChpFc7PHzBAHgTzM","entryType":"resource","startTime":2030.8000000715256,"duration":123.19999992847443,"initiatorType":"img","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2030.8000000715256,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2154,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://data.pendo.io/data/guide.js/dc5c6fad-c3ae-4441-49ce-0ae37103aed7?id=4&jzb=eJxtVE1zo0gM_S-csQ3kYzK-eexkK1WTneyQvezFJdMCK2m6KXXDlHdr_vuoAUPGmxP0k1p6kp76v6gjR97yo4rWUbpMb6_vsturKI6gKGxr_IDLuWUtf0fvG7derRwaz6fl-CG7IudadCtxrNGDAg_RegoefukyQetwzDpBbDUKAKomI0dyO-C3J6sELEE7jCOn3v6EGp14jbkrG49_r9CBK5gavzyw_SHh4-oIi8H6gRMjFP6MNyd_tGapXsFMEReFpgt7qcG9fRDLBJJxdASXi-2FwTgJTtYIU8-tMB9NOTr3AfwdGw2n9-gTeqZio5H9BBeS5dsPg-y2YTbR-iaOGravWPgRyK7iqCR2_r4LPKgO_cySNFsk6SK9fsmydXK3vsmWn9L0Lvn8T9_leyON16geTV5LrnOr6yELn7b9BEyrdc_sGbm0XIMp8LGf-jvWz2xL0hM-hurF0ZfyvdU4Ur39NFbfN2RowDmSlim0UAXyaPZ_fIl-ToKctdRzf2Cc1UHuCYxcE_MQh9wLE4hwJ4d745EbJoeXlndlPUv-S3PeugaNCrFHrLh0EiBHXebI3cxJyjJB03W2P7SOjJS7l9HsoS2lgGB9IZQNCR7DNID1aaNsI0TPdXjrQT9hfZDRi2tHsig-LAyKyjtU98y2t7Dwm-HfdThfAw4JB9-xrVs5kTR3REV7oHeiyK9YCjrM3nIFhv6FEG8ThnOV3IjeZIjfzA6lcyoPLZr7IWlJIT9Y3rm_G4daejUUFEh1KGqpbYh2ziBiHoA59SINgepGmKOaLoRyhGYp0--VPtD__Ra5r-D89lRoFAZBKL3rLEsl7jmFeZ_DbjXIykgFfdbWeVsj72wNNG8siYIq7pvQP0Maire4In9sD_ErMcSNKJBV609xh1ygnoHQ7tYfJV0X-hKuV9ZW8ugFMu54sMBqXI_0cyIgucJKlBxkmn-1GHaxt17filUa_4W0JlNtlJKZz5X9z_KA4FuWkV1sqni8w4a35UhNWNOxYNm8Blj6tpn2TyB54PuX5WaVXK-yJAuvt_AMmxzgZXqXLpO9jFNFP3_-Ams9Pkk&v=2.181.0_prod&ct=1682429095856","entryType":"resource","startTime":2032.7000000476837,"duration":142.10000002384186,"initiatorType":"script","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2032.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2174.8000000715256,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":0,"serverTiming":[]},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":2270.2000000476837,"duration":38.199999928474426,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2270.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2308.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2295.2000000476837,"duration":0},{"name":"IssueList-Body-vcsd-end-pre-timeout","entryType":"mark","startTime":2297,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues-stats/?groups=3622378978&groups=4050674861&groups=3899312585&groups=3915010727&groups=3899249705&groups=4057453162&groups=3935262512&groups=3935139508&groups=4057455327&groups=4012287646&groups=3920436468&groups=3991114523&groups=4046641574&groups=4090532740&groups=3670893465&groups=3899249419&groups=4077639192&groups=4074870668&groups=3899261560&groups=3919710734&groups=3248727627&groups=4113029290&groups=3911631555&groups=3740335939&groups=3899257705&query=is%3Aunresolved&statsPeriod=14d","entryType":"resource","startTime":2304.5,"duration":696.6000000238419,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2304.5,"domainLookupStart":2304.5,"domainLookupEnd":2304.5,"connectStart":2304.5,"secureConnectionStart":2304.5,"connectEnd":2304.5,"requestStart":2305.7000000476837,"responseStart":2995.399999976158,"responseEnd":3001.100000023842,"transferSize":4468,"encodedBodySize":4168,"decodedBodySize":17200,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2314.5,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/issues-count/?query=is%3Aunresolved%20is%3Afor_review%20assigned_or_suggested%3A%5Bme%2C%20none%5D&query=is%3Aignored&query=is%3Areprocessing&statsPeriod=14d","entryType":"resource","startTime":2320.399999976158,"duration":707.3000000715256,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2320.399999976158,"domainLookupStart":2320.399999976158,"domainLookupEnd":2320.399999976158,"connectStart":2320.399999976158,"secureConnectionStart":2320.399999976158,"connectEnd":2320.399999976158,"requestStart":2321.399999976158,"responseStart":3026.3000000715256,"responseEnd":3027.7000000476837,"transferSize":405,"encodedBodySize":105,"decodedBodySize":105,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2328.3000000715256,"duration":0},{"name":"https://sentry.sentry.io/api/0/organizations/sentry/replay-count/?project=11276&query=issue.id%3A%5B3740335939%5D&statsPeriod=14d","entryType":"resource","startTime":2386,"duration":166.30000007152557,"initiatorType":"fetch","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2386,"domainLookupStart":2386,"domainLookupEnd":2386,"connectStart":2386,"secureConnectionStart":2386,"connectEnd":2386,"requestStart":2388.7000000476837,"responseStart":2551.600000023842,"responseEnd":2552.3000000715256,"transferSize":317,"encodedBodySize":17,"decodedBodySize":17,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":2399.100000023842,"duration":0},{"name":"VCD [IssueList-Body] #1","entryType":"measure","startTime":2399.100000023842,"duration":50},{"name":"IssueList-Body-vcsd-end","entryType":"mark","startTime":2449.100000023842,"duration":0},{"name":"https://api2.amplitude.com/2/httpapi","entryType":"resource","startTime":2457,"duration":542.3999999761581,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":2457,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":2999.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://reload.getsentry.net/event/","entryType":"resource","startTime":3219.7000000476837,"duration":33.89999997615814,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":3219.7000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":3253.600000023842,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":3251.100000023842,"duration":0},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":3316.2000000476837,"duration":36.799999952316284,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":3316.2000000476837,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":3353,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":3326.7000000476837,"duration":0},{"name":"keydown","entryType":"first-input","startTime":4007.899999976158,"duration":8,"processingStart":4011.600000023842,"processingEnd":4013,"cancelable":true},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":4077.600000023842,"duration":0},{"name":"https://sentry.sentry.io/avatar/ca66558c539e408eaaa450a096e24ebf/?s=120","entryType":"resource","startTime":4166,"duration":92.5,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4166,"domainLookupStart":4166,"domainLookupEnd":4166,"connectStart":4166,"secureConnectionStart":4166,"connectEnd":4166,"requestStart":4168.300000071526,"responseStart":4255.200000047684,"responseEnd":4258.5,"transferSize":6105,"encodedBodySize":5805,"decodedBodySize":5805,"responseStatus":200,"serverTiming":[]},{"name":"https://sentry.sentry.io/avatar/ba59b4e18344480794c5a623dbf6ef6a/?s=120","entryType":"resource","startTime":4166.100000023842,"duration":101,"initiatorType":"img","nextHopProtocol":"http/1.1","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4166.100000023842,"domainLookupStart":4166.100000023842,"domainLookupEnd":4166.100000023842,"connectStart":4166.100000023842,"secureConnectionStart":4166.100000023842,"connectEnd":4166.100000023842,"requestStart":4167.700000047684,"responseStart":4266.5,"responseEnd":4267.100000023842,"transferSize":21805,"encodedBodySize":21505,"decodedBodySize":21505,"responseStatus":200,"serverTiming":[]},{"name":"IssueList-Body-vcsd-start","entryType":"mark","startTime":4175,"duration":0},{"name":"https://api2.amplitude.com/2/httpapi","entryType":"resource","startTime":4317,"duration":258.7000000476837,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4317,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4575.700000047684,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://reload.getsentry.net/metric/","entryType":"resource","startTime":4323.800000071526,"duration":34.799999952316284,"initiatorType":"xmlhttprequest","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4323.800000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4358.600000023842,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":201,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":4454.5,"duration":75.39999997615814,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":4454.5,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":4529.899999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":5931.800000071526,"duration":69.19999992847443,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":5931.800000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":6001,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":9973.300000071526,"duration":45.09999990463257,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":9973.300000071526,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":10018.399999976158,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]},{"name":"https://o1.ingest.sentry.io/api/11276/envelope/?sentry_key=c51734c603c4430eb57cb0a5728a479d&sentry_version=7&sentry_client=sentry.javascript.react%2F7.46.0","entryType":"resource","startTime":68177.70000004768,"duration":100.19999992847443,"initiatorType":"fetch","nextHopProtocol":"","renderBlockingStatus":"non-blocking","workerStart":0,"redirectStart":0,"redirectEnd":0,"fetchStart":68177.70000004768,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"secureConnectionStart":0,"connectEnd":0,"requestStart":0,"responseStart":0,"responseEnd":68277.89999997616,"transferSize":0,"encodedBodySize":0,"decodedBodySize":0,"responseStatus":200,"serverTiming":[]}]', -); - -const half = data.slice(0, Math.round(data.length / 2)); -// add tests -new Benchmark.Suite() - .add('new list (old)', function () { - const entries = fns.dedupePerformanceEntries([], data); - }) - .add('new list (new)', function () { - const entries = fnsNew.dedupePerformanceEntries([], data); - }) - .add('diff list half (old)', function () { - const entries = fns.dedupePerformanceEntries(half, data); - }) - .add('diff list half (new)', function () { - const entries = fnsNew.dedupePerformanceEntries(half, data); - }) - .add('same list (old)', function () { - const entries = fns.dedupePerformanceEntries(data, data); - }) - .add('same list half (new)', function () { - const entries = fnsNew.dedupePerformanceEntries(data, data); - }) - // @ts-ignore - .on('error', function (event) { - // @ts-ignore - console.log(event.target.error); - }) - // @ts-ignore - .on('cycle', function (event) { - // @ts-ignore - console.log(String(event.target)); - }) - .on('complete', function () { - // @ts-ignore - console.log(`Fastest is ${this.filter('fastest').map('name')}`); - }) - .run({ async: false }); - -module.exports = {}; diff --git a/packages/replay/src/util/dedupePerformanceEntries.new.ts b/packages/replay/src/util/dedupePerformanceEntries.new.ts deleted file mode 100644 index 94d430a78182..000000000000 --- a/packages/replay/src/util/dedupePerformanceEntries.new.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { PerformanceNavigationTiming, PerformancePaintTiming } from '../types'; - -const NAVIGATION_ENTRY_KEYS: Array = [ - 'name', - 'type', - 'startTime', - 'transferSize', - 'duration', -]; - -/** - * There are some difficulties diagnosing why there are duplicate navigation - * entries. We've witnessed several intermittent results: - * - duplicate entries have duration = 0 - * - duplicate entries are the same object reference - * - none of the above - * - * Compare the values of several keys to determine if the entries are duplicates or not. - */ -// TODO (high-prio): Figure out wth is returned here -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -function dedupePerformanceEntries( - currentList: PerformanceEntryList, - newList: PerformanceEntryList, -): PerformanceEntryList { - if (!currentList.length && !newList.length) { - return []; - } - - // if (currentList.length === 0) { - // return newList.slice().sort((a, b) => a.startTime - b.startTime); - // } - - // Partition `currentList` into 3 different lists based on entryType - const [existingNavigationEntries, existingLcpEntries, existingEntries] = currentList.reduce( - (acc: [PerformanceNavigationTiming[], PerformancePaintTiming[], PerformanceEntryList], entry) => { - if (entry.entryType === 'navigation') { - acc[0].push(entry as PerformanceNavigationTiming); - } else if (entry.entryType === 'largest-contentful-paint') { - acc[1].push(entry as PerformancePaintTiming); - } else { - acc[2].push(entry); - } - return acc; - }, - [[], [], []], - ); - - const newEntries: PerformanceEntryList = []; - const newNavigationEntries: PerformanceNavigationTiming[] = []; - let newLcpEntry: PerformancePaintTiming | undefined = existingLcpEntries[existingLcpEntries.length - 1]; // Take the last element as list is sorted - let foundNewLcp = false; - - for (let i = newList.length - 1; i >= 0; i--) { - const entry = newList[i]; - if (entry.entryType !== 'navigation' && entry.entryType !== 'largest-contentful-paint') { - newEntries.push(entry); - continue; - } - - if (entry.entryType === 'navigation') { - const navigationEntry = entry as PerformanceNavigationTiming; - - if (entry.duration <= 0) { - // Ignore any navigation entries with duration 0, as they are likely duplicates - continue; - } - - // Check if the navigation entry is contained in currentList or newList - if ( - // Ensure new entry does not already exist in existing entries - !existingNavigationEntries.find(a => { - return a === navigationEntry || NAVIGATION_ENTRY_KEYS.every(key => a[key] === navigationEntry[key]); - }) && - // Ensure new entry does not already exist in new list of navigation entries - !newNavigationEntries.find(a => { - return a === navigationEntry || NAVIGATION_ENTRY_KEYS.every(key => a[key] === navigationEntry[key]); - }) - ) { - newNavigationEntries.push(navigationEntry); - } - - // Otherwise this navigation entry is considered a duplicate and is thrown away - continue; - } - - if (entry.entryType === 'largest-contentful-paint' && !foundNewLcp) { - // We want the latest LCP event only - if (!newLcpEntry || newLcpEntry.startTime < entry.startTime) { - newLcpEntry = entry; - foundNewLcp = true; - } - continue; - } - } - - return newEntries - .concat(newNavigationEntries, existingEntries, existingNavigationEntries, newLcpEntry ? [newLcpEntry] : []) - .sort((a, b) => a.startTime - b.startTime); -} - -exports.dedupePerformanceEntries = dedupePerformanceEntries; From 63c35350f355c0c3ace90bcd75954f0f15bbe23a Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 09:24:39 -0400 Subject: [PATCH 03/11] test: remove unused test --- .../test/unit/profiling/integration.test.ts | 151 ------------------ 1 file changed, 151 deletions(-) delete mode 100644 packages/browser/test/unit/profiling/integration.test.ts diff --git a/packages/browser/test/unit/profiling/integration.test.ts b/packages/browser/test/unit/profiling/integration.test.ts deleted file mode 100644 index 1ea59ee7068e..000000000000 --- a/packages/browser/test/unit/profiling/integration.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { getCurrentHub } from '@sentry/browser'; -import type { Event } from '@sentry/types'; -import { TextDecoder, TextEncoder } from 'util'; - -// @ts-ignore patch the encoder on the window, else importing JSDOM fails (deleted in afterAll) -const patchedEncoder = (!global.window.TextEncoder && (global.window.TextEncoder = TextEncoder)) || true; -// @ts-ignore patch the encoder on the window, else importing JSDOM fails (deleted in afterAll) -const patchedDecoder = (!global.window.TextDecoder && (global.window.TextDecoder = TextDecoder)) || true; - -import { JSDOM } from 'jsdom'; - -import { PROFILING_EVENT_CACHE } from '../../../src/profiling/cache'; -import { BrowserProfilingIntegration } from '../../../src/profiling/integration'; -import { sendProfile } from '../../../src/profiling/sendProfile'; - -// @ts-ignore store a reference so we can reset it later -const globalDocument = global.document; -// @ts-ignore store a reference so we can reset it later -const globalWindow = global.window; -// @ts-ignore store a reference so we can reset it later -const globalLocation = global.location; - -describe('BrowserProfilingIntegration', () => { - beforeEach(() => { - // Clear profiling event cache - PROFILING_EVENT_CACHE.clear(); - - const dom = new JSDOM(); - // @ts-ignore need to override global document - global.document = dom.window.document; - // @ts-ignore need to override global document - global.window = dom.window; - // @ts-ignore need to override global document - global.location = dom.window.location; - }); - - // Reset back to previous values - afterEach(() => { - // @ts-ignore need to override global document - global.document = globalDocument; - // @ts-ignore need to override global document - global.window = globalWindow; - // @ts-ignore need to override global document - global.location = globalLocation; - }); - - afterAll(() => { - // @ts-ignore patch the encoder on the window, else importing JSDOM fails - patchedEncoder && delete global.window.TextEncoder; - // @ts-ignore patch the encoder on the window, else importing JSDOM fails - patchedDecoder && delete global.window.TextDecoder; - }); - - it('does not store event in profiling event cache if context["profile"]["profile_id"] is not present', () => { - const integration = new BrowserProfilingIntegration(); - const event: Event = { - contexts: {}, - }; - integration.handleGlobalEvent(event); - expect(PROFILING_EVENT_CACHE.size()).toBe(0); - }); - - it('stores event in profiling event cache if context["profile"]["profile_id"] is present', () => { - const integration = new BrowserProfilingIntegration(); - const event: Event = { - contexts: { - profile: { - profile_id: 'profile_id', - }, - }, - }; - integration.handleGlobalEvent(event); - expect(PROFILING_EVENT_CACHE.get(event.contexts!.profile!.profile_id as string)).toBe(event); - }); - - it('sending profile evicts it from the LRU cache', () => { - const hub = getCurrentHub(); - const client: any = { - getDsn() { - return {}; - }, - getTransport() { - return { - send() {}, - }; - }, - }; - - hub.bindClient(client); - - const integration = new BrowserProfilingIntegration(); - const event: Event = { - type: 'transaction', - contexts: { - profile: { - profile_id: 'profile_id', - }, - }, - }; - - integration.handleGlobalEvent(event); - - sendProfile('profile_id', { - resources: [], - samples: [], - stacks: [], - frames: [], - profile_id: 'profile_id', - }); - - expect(PROFILING_EVENT_CACHE.get('profile_id')).toBe(undefined); - }); -}); - -describe('ProfilingEventCache', () => { - beforeEach(() => { - PROFILING_EVENT_CACHE.clear(); - }); - - it('caps the size of the profiling event cache', () => { - for (let i = 0; i <= 21; i++) { - const integration = new BrowserProfilingIntegration(); - const event: Event = { - contexts: { - profile: { - profile_id: `profile_id_${i}`, - }, - }, - }; - integration.handleGlobalEvent(event); - } - expect(PROFILING_EVENT_CACHE.size()).toBe(20); - // Evicts the first item in the cache - expect(PROFILING_EVENT_CACHE.get('profile_id_0')).toBe(undefined); - }); - - it('handles collision by replacing the value', () => { - PROFILING_EVENT_CACHE.add('profile_id_0', {}); - const second = {}; - PROFILING_EVENT_CACHE.add('profile_id_0', second); - - expect(PROFILING_EVENT_CACHE.get('profile_id_0')).toBe(second); - expect(PROFILING_EVENT_CACHE.size()).toBe(1); - }); - - it('clears cache', () => { - PROFILING_EVENT_CACHE.add('profile_id_0', {}); - PROFILING_EVENT_CACHE.clear(); - expect(PROFILING_EVENT_CACHE.size()).toBe(0); - }); -}); From efada28beade4f86090fbcfcf6f56f31f52d51ba Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 09:49:09 -0400 Subject: [PATCH 04/11] test: remove circular dep --- .../browser/src/profiling/hubextensions.ts | 6 ++++-- packages/browser/src/profiling/integration.ts | 18 +----------------- packages/browser/src/profiling/utils.ts | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 292d4e672ae6..57e544942a45 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -4,14 +4,14 @@ import type { Transaction } from '@sentry/types'; import { logger, uuid4 } from '@sentry/utils'; import { WINDOW } from '../helpers'; -import { addToProfileQueue } from './integration'; import type { JSSelfProfile, JSSelfProfiler, JSSelfProfilerConstructor, ProcessedJSSelfProfile, } from './jsSelfProfiling'; -import { isValidSampleRate } from './utils'; +import { addToProfileQueue , isValidSampleRate } from './utils'; + export const MAX_PROFILE_DURATION_MS = 30_000; // Keep a flag value to avoid re-initializing the profiler constructor. If it fails @@ -270,3 +270,5 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact transaction.finish = profilingWrappedTransactionFinish; return transaction; } + + diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index c8cbe4617161..1f5051371c5c 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -4,24 +4,8 @@ import { logger } from '@sentry/utils'; import type { BrowserClient } from './../client'; import { wrapTransactionWithProfiling } from './hubextensions'; -import type { ProcessedJSSelfProfile } from './jsSelfProfiling'; import type { ProfiledEvent } from './utils'; -import { addProfilesToEnvelope, createProfilingEvent, findProfiledTransactionsFromEnvelope } from './utils'; - -const MAX_PROFILE_QUEUE_SIZE = 50; -const PROFILE_QUEUE: ProcessedJSSelfProfile[] = []; - -/** - * Adds the profile to the queue of profiles to be sent - */ -export function addToProfileQueue(profile: ProcessedJSSelfProfile): void { - PROFILE_QUEUE.push(profile); - - // We only want to keep the last n profiles in the queue. - if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_SIZE) { - PROFILE_QUEUE.shift(); - } -} +import { addProfilesToEnvelope, createProfilingEvent, findProfiledTransactionsFromEnvelope,PROFILE_QUEUE } from './utils'; /** * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 1cf1f97d65bc..4c4f1a175fe8 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -450,3 +450,17 @@ export function createProfilingEvent(profile: ProcessedJSSelfProfile, event: Pro return createProfilePayload(event, profile); } + +const MAX_PROFILE_QUEUE_SIZE = 50; +export const PROFILE_QUEUE: ProcessedJSSelfProfile[] = []; +/** + * Adds the profile to the queue of profiles to be sent + */ +export function addToProfileQueue(profile: ProcessedJSSelfProfile): void { + PROFILE_QUEUE.push(profile); + + // We only want to keep the last n profiles in the queue. + if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_SIZE) { + PROFILE_QUEUE.shift(); + } +} \ No newline at end of file From 9818ee35db3257432699af9f376fbb5e2c1c41d0 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 10:27:26 -0400 Subject: [PATCH 05/11] lint: prettier --- packages/browser/src/profiling/hubextensions.ts | 5 +---- packages/browser/src/profiling/integration.ts | 7 ++++++- packages/browser/src/profiling/utils.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 57e544942a45..651e965bad46 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -10,8 +10,7 @@ import type { JSSelfProfilerConstructor, ProcessedJSSelfProfile, } from './jsSelfProfiling'; -import { addToProfileQueue , isValidSampleRate } from './utils'; - +import { addToProfileQueue, isValidSampleRate } from './utils'; export const MAX_PROFILE_DURATION_MS = 30_000; // Keep a flag value to avoid re-initializing the profiler constructor. If it fails @@ -270,5 +269,3 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact transaction.finish = profilingWrappedTransactionFinish; return transaction; } - - diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index 1f5051371c5c..05bd15e84d96 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -5,7 +5,12 @@ import { logger } from '@sentry/utils'; import type { BrowserClient } from './../client'; import { wrapTransactionWithProfiling } from './hubextensions'; import type { ProfiledEvent } from './utils'; -import { addProfilesToEnvelope, createProfilingEvent, findProfiledTransactionsFromEnvelope,PROFILE_QUEUE } from './utils'; +import { + addProfilesToEnvelope, + createProfilingEvent, + findProfiledTransactionsFromEnvelope, + PROFILE_QUEUE, +} from './utils'; /** * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 4c4f1a175fe8..bb4a0f0171cf 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -463,4 +463,4 @@ export function addToProfileQueue(profile: ProcessedJSSelfProfile): void { if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_SIZE) { PROFILE_QUEUE.shift(); } -} \ No newline at end of file +} From f0be0115303aa25771a52913baa8e944079ba5ec Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 13:45:02 -0400 Subject: [PATCH 06/11] fix: discard profile if it has no frames --- packages/browser/src/profiling/hubextensions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 651e965bad46..e39568cc183b 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -216,6 +216,12 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact return null; } + // Discard a profile if it has no frames - happens if code was idling or sampling + // did not collect any frames. + if(!p.frames.length){ + return null; + } + addToProfileQueue({ ...p, profile_id: profileId }); return null; }) From 25d30ae60c8cec2a5e7a9c246d968f0c89ee3ba7 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 14:00:10 -0400 Subject: [PATCH 07/11] fix: use map instead of queue --- .../browser/src/profiling/hubextensions.ts | 5 ++-- packages/browser/src/profiling/integration.ts | 25 ++++++------------- packages/browser/src/profiling/utils.ts | 18 ++++++------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index e39568cc183b..c42438103b2c 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -10,7 +10,7 @@ import type { JSSelfProfilerConstructor, ProcessedJSSelfProfile, } from './jsSelfProfiling'; -import { addToProfileQueue, isValidSampleRate } from './utils'; +import { addProfileToMap,isValidSampleRate } from './utils'; export const MAX_PROFILE_DURATION_MS = 30_000; // Keep a flag value to avoid re-initializing the profiler constructor. If it fails @@ -222,7 +222,8 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact return null; } - addToProfileQueue({ ...p, profile_id: profileId }); + + addProfileToMap({...p, profile_id: profileId }); return null; }) .catch(error => { diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index 05bd15e84d96..d04795481662 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -9,7 +9,7 @@ import { addProfilesToEnvelope, createProfilingEvent, findProfiledTransactionsFromEnvelope, - PROFILE_QUEUE, + PROFILE_MAP, } from './utils'; /** @@ -39,8 +39,7 @@ export class BrowserProfilingIntegration implements Integration { client.on('beforeEnvelope', (envelope): void => { // if not profiles are in queue, there is nothing to add to the envelope. - - if (!PROFILE_QUEUE.length) { + if (!PROFILE_MAP.size) { return; } @@ -56,7 +55,7 @@ export class BrowserProfilingIntegration implements Integration { profiledTransaction && profiledTransaction.contexts && profiledTransaction.contexts['profile'] && - profiledTransaction.contexts['profile']['profile_id']; + profiledTransaction.contexts['profile']['profile_id'] as string if (!profile_id) { throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); @@ -67,23 +66,15 @@ export class BrowserProfilingIntegration implements Integration { delete profiledTransaction.contexts.profile; } - // We need to find both a profile and a transaction event for the same profile_id. - const profileIndex = PROFILE_QUEUE.findIndex(p => p.profile_id === profile_id); - if (profileIndex === -1) { - __DEBUG_BUILD__ && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); - continue; - } - - const cpuProfile = PROFILE_QUEUE[profileIndex]; - if (!cpuProfile) { + const profile = PROFILE_MAP.get(profile_id); + if (!profile) { __DEBUG_BUILD__ && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); continue; } - // Remove the profile from the queue. - PROFILE_QUEUE.splice(profileIndex, 1); - const profileEvent = createProfilingEvent(cpuProfile, profiledTransaction as ProfiledEvent); - + PROFILE_MAP.delete(profile_id); + const profileEvent = createProfilingEvent(profile, profiledTransaction as ProfiledEvent); + if (profileEvent) { profilesToAddToEnvelope.push(profileEvent); } diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index bb4a0f0171cf..2aa6293ef435 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -451,16 +451,16 @@ export function createProfilingEvent(profile: ProcessedJSSelfProfile, event: Pro return createProfilePayload(event, profile); } -const MAX_PROFILE_QUEUE_SIZE = 50; -export const PROFILE_QUEUE: ProcessedJSSelfProfile[] = []; + +export const PROFILE_MAP: Map = new Map(); /** - * Adds the profile to the queue of profiles to be sent + * */ -export function addToProfileQueue(profile: ProcessedJSSelfProfile): void { - PROFILE_QUEUE.push(profile); +export function addProfileToMap(profile: ProcessedJSSelfProfile): void{ + PROFILE_MAP.set(profile.profile_id, profile); - // We only want to keep the last n profiles in the queue. - if (PROFILE_QUEUE.length > MAX_PROFILE_QUEUE_SIZE) { - PROFILE_QUEUE.shift(); + if(PROFILE_MAP.size > 30){ + const last: string = [...PROFILE_MAP.keys()].pop() as string; + PROFILE_MAP.delete(last); } -} +} \ No newline at end of file From c7078fbd42dd820040424cc880dce3c81fde3447 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 22 Jun 2023 18:00:40 -0400 Subject: [PATCH 08/11] ref: cleanup types and drop separate profile_id --- .../browser/src/profiling/hubextensions.ts | 17 +------ packages/browser/src/profiling/integration.ts | 9 ++-- .../browser/src/profiling/jsSelfProfiling.ts | 7 --- packages/browser/src/profiling/utils.ts | 48 +++++++++---------- 4 files changed, 31 insertions(+), 50 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index c42438103b2c..250b4eb41b10 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -8,7 +8,6 @@ import type { JSSelfProfile, JSSelfProfiler, JSSelfProfilerConstructor, - ProcessedJSSelfProfile, } from './jsSelfProfiling'; import { addProfileToMap,isValidSampleRate } from './utils'; @@ -163,7 +162,7 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact // event of an error or user mistake (calling transaction.finish multiple times), it is important that the behavior of onProfileHandler // is idempotent as we do not want any timings or profiles to be overriden by the last call to onProfileHandler. // After the original finish method is called, the event will be reported through the integration and delegated to transport. - const processedProfile: ProcessedJSSelfProfile | null = null; + const processedProfile: JSSelfProfile | null = null; /** * Idempotent handler for profile stop @@ -211,19 +210,7 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact return null; } - // If a profile has less than 2 samples, it is not useful and should be discarded. - if (p.samples.length < 2) { - return null; - } - - // Discard a profile if it has no frames - happens if code was idling or sampling - // did not collect any frames. - if(!p.frames.length){ - return null; - } - - - addProfileToMap({...p, profile_id: profileId }); + addProfileToMap(profileId, p); return null; }) .catch(error => { diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index d04795481662..a559cdff53ae 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -58,11 +58,12 @@ export class BrowserProfilingIntegration implements Integration { profiledTransaction.contexts['profile']['profile_id'] as string if (!profile_id) { - throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); + __DEBUG_BUILD__ && logger.log('[Profiling] cannot find profile for a transaction without a profile context'); + continue; } // Remove the profile from the transaction context before sending, relay will take care of the rest. - if (profiledTransaction && profiledTransaction.contexts && profiledTransaction.contexts['.profile']) { + if (profiledTransaction && profiledTransaction.contexts && profiledTransaction.contexts['profile']) { delete profiledTransaction.contexts.profile; } @@ -73,8 +74,8 @@ export class BrowserProfilingIntegration implements Integration { } PROFILE_MAP.delete(profile_id); - const profileEvent = createProfilingEvent(profile, profiledTransaction as ProfiledEvent); - + const profileEvent = createProfilingEvent(profile_id, profile, profiledTransaction as ProfiledEvent); + if (profileEvent) { profilesToAddToEnvelope.push(profileEvent); } diff --git a/packages/browser/src/profiling/jsSelfProfiling.ts b/packages/browser/src/profiling/jsSelfProfiling.ts index efa4a0a0a0bc..d2991e6ddb63 100644 --- a/packages/browser/src/profiling/jsSelfProfiling.ts +++ b/packages/browser/src/profiling/jsSelfProfiling.ts @@ -26,10 +26,6 @@ export type JSSelfProfile = { samples: JSSelfProfileSample[]; }; -export interface ProcessedJSSelfProfile extends JSSelfProfile { - profile_id: string; -} - type BufferFullCallback = (trace: JSSelfProfile) => void; export interface JSSelfProfiler { @@ -50,6 +46,3 @@ declare global { } } -export interface RawThreadCpuProfile extends JSSelfProfile { - profile_id: string; -} diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 2aa6293ef435..09c0f44e5046 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -6,7 +6,7 @@ import type { Profile, ThreadCpuProfile } from '@sentry/types/src/profiling'; import { forEachEnvelopeItem, GLOBAL_OBJ, logger, uuid4 } from '@sentry/utils'; import { WINDOW } from '../helpers'; -import type { JSSelfProfile, JSSelfProfileStack, ProcessedJSSelfProfile } from './jsSelfProfiling'; +import type { JSSelfProfile, JSSelfProfileStack } from './jsSelfProfiling'; const MS_TO_NS = 1e6; // Use 0 as main thread id which is identical to threadId in node:worker_threads @@ -64,9 +64,7 @@ if (isUserAgentData(userAgentData)) { .catch(e => void e); } -function isProcessedJSSelfProfile( - profile: ThreadCpuProfile | ProcessedJSSelfProfile, -): profile is ProcessedJSSelfProfile { +function isProcessedJSSelfProfile(profile: ThreadCpuProfile | JSSelfProfile): profile is JSSelfProfile { return !('thread_metadata' in profile); } @@ -75,7 +73,7 @@ function isProcessedJSSelfProfile( /** * */ -export function enrichWithThreadInformation(profile: ThreadCpuProfile | ProcessedJSSelfProfile): ThreadCpuProfile { +export function enrichWithThreadInformation(profile: ThreadCpuProfile | JSSelfProfile): ThreadCpuProfile { if (!isProcessedJSSelfProfile(profile)) { return profile; } @@ -87,7 +85,7 @@ export function enrichWithThreadInformation(profile: ThreadCpuProfile | Processe // by the integration before the event is processed by other integrations. export interface ProfiledEvent extends Event { sdkProcessingMetadata: { - profile?: ProcessedJSSelfProfile; + profile?: JSSelfProfile; }; } @@ -120,7 +118,11 @@ function getTraceId(event: Event): string { /** * Creates a profiling event envelope from a Sentry event. */ -export function createProfilePayload(event: ProfiledEvent, processedProfile: ProcessedJSSelfProfile): Profile { +export function createProfilePayload( + event: ProfiledEvent, + processedProfile: JSSelfProfile, + profile_id: string, +): Profile { if (event.type !== 'transaction') { // createProfilingEventEnvelope should only be called for transactions, // we type guard this behavior with isProfiledTransactionEvent. @@ -133,17 +135,13 @@ export function createProfilePayload(event: ProfiledEvent, processedProfile: Pro ); } - if (!processedProfile.profile_id) { - throw new TypeError('Profile is missing profile_id'); - } - const traceId = getTraceId(event); const enrichedThreadProfile = enrichWithThreadInformation(processedProfile); const transactionStartMs = typeof event.start_timestamp === 'number' ? event.start_timestamp * 1000 : Date.now(); const transactionEndMs = typeof event.timestamp === 'number' ? event.timestamp * 1000 : Date.now(); const profile: Profile = { - event_id: processedProfile.profile_id, + event_id: profile_id, timestamp: new Date(transactionStartMs).toISOString(), platform: 'javascript', version: '1', @@ -420,8 +418,8 @@ export function isValidSampleRate(rate: unknown): boolean { return true; } -function isValidProfile(profile: ProcessedJSSelfProfile): profile is ProcessedJSSelfProfile & { profile_id: string } { - if (profile.samples.length <= 1) { +function isValidProfile(profile: JSSelfProfile): profile is JSSelfProfile & { profile_id: string } { + if (profile.samples.length < 2) { if (__DEBUG_BUILD__) { // Log a warning if the profile has less than 2 samples so users can know why // they are not seeing any profiling data and we cant avoid the back and forth @@ -431,7 +429,10 @@ function isValidProfile(profile: ProcessedJSSelfProfile): profile is ProcessedJS return false; } - if (!profile.profile_id) { + if (!profile.frames.length) { + if(__DEBUG_BUILD__) { + logger.log('[Profiling] Discarding profile because it contains no frames'); + } return false; } @@ -443,24 +444,23 @@ function isValidProfile(profile: ProcessedJSSelfProfile): profile is ProcessedJS * @param event * @returns {Profile | null} */ -export function createProfilingEvent(profile: ProcessedJSSelfProfile, event: ProfiledEvent): Profile | null { +export function createProfilingEvent(profile_id: string, profile: JSSelfProfile, event: ProfiledEvent): Profile | null { if (!isValidProfile(profile)) { return null; } - return createProfilePayload(event, profile); + return createProfilePayload(event, profile, profile_id); } - -export const PROFILE_MAP: Map = new Map(); +export const PROFILE_MAP: Map = new Map(); /** * */ -export function addProfileToMap(profile: ProcessedJSSelfProfile): void{ - PROFILE_MAP.set(profile.profile_id, profile); +export function addProfileToMap(profile_id: string, profile: JSSelfProfile): void { + PROFILE_MAP.set(profile_id, profile); - if(PROFILE_MAP.size > 30){ - const last: string = [...PROFILE_MAP.keys()].pop() as string; + if (PROFILE_MAP.size > 30) { + const last: string = PROFILE_MAP.keys().next().value; PROFILE_MAP.delete(last); } -} \ No newline at end of file +} From 5c29fa2d7537822395a3a444ec6fb445edcbef48 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 23 Jun 2023 09:31:03 -0400 Subject: [PATCH 09/11] fix: run prettier --- packages/browser/src/profiling/hubextensions.ts | 15 ++++++++------- packages/browser/src/profiling/integration.ts | 12 +++++------- packages/browser/src/profiling/jsSelfProfiling.ts | 1 - packages/browser/src/profiling/utils.ts | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 250b4eb41b10..5b45b593b2e2 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -4,12 +4,8 @@ import type { Transaction } from '@sentry/types'; import { logger, uuid4 } from '@sentry/utils'; import { WINDOW } from '../helpers'; -import type { - JSSelfProfile, - JSSelfProfiler, - JSSelfProfilerConstructor, -} from './jsSelfProfiling'; -import { addProfileToMap,isValidSampleRate } from './utils'; +import type { JSSelfProfile, JSSelfProfiler, JSSelfProfilerConstructor } from './jsSelfProfiling'; +import { addProfileToMap, isValidSampleRate } from './utils'; export const MAX_PROFILE_DURATION_MS = 30_000; // Keep a flag value to avoid re-initializing the profiler constructor. If it fails @@ -82,7 +78,12 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact // Prefer sampler to sample rate if both are provided. if (typeof profilesSampler === 'function') { - profilesSampleRate = profilesSampler({ transactionContext: transaction.toContext() }); + const transactionContext = transaction.toContext(); + profilesSampleRate = profilesSampler({ + parentSampled: transactionContext.parentSampled, + transactionContext: transactionContext, + ...transaction.getDynamicSamplingContext(), + }); } // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index a559cdff53ae..eee2691bddc6 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -39,7 +39,7 @@ export class BrowserProfilingIntegration implements Integration { client.on('beforeEnvelope', (envelope): void => { // if not profiles are in queue, there is nothing to add to the envelope. - if (!PROFILE_MAP.size) { + if (!PROFILE_MAP['size']) { return; } @@ -51,14 +51,12 @@ export class BrowserProfilingIntegration implements Integration { const profilesToAddToEnvelope: Profile[] = []; for (const profiledTransaction of profiledTransactionEvents) { - const profile_id = - profiledTransaction && - profiledTransaction.contexts && - profiledTransaction.contexts['profile'] && - profiledTransaction.contexts['profile']['profile_id'] as string + const context = profiledTransaction && profiledTransaction.contexts; + const profile_id = context && context['profile'] && (context['profile']['profile_id'] as string); if (!profile_id) { - __DEBUG_BUILD__ && logger.log('[Profiling] cannot find profile for a transaction without a profile context'); + __DEBUG_BUILD__ && + logger.log('[Profiling] cannot find profile for a transaction without a profile context'); continue; } diff --git a/packages/browser/src/profiling/jsSelfProfiling.ts b/packages/browser/src/profiling/jsSelfProfiling.ts index d2991e6ddb63..8dc981d3d5d3 100644 --- a/packages/browser/src/profiling/jsSelfProfiling.ts +++ b/packages/browser/src/profiling/jsSelfProfiling.ts @@ -45,4 +45,3 @@ declare global { Profiler: typeof JSSelfProfilerConstructor | undefined; } } - diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 09c0f44e5046..6c9b4d8ed6b9 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -430,7 +430,7 @@ function isValidProfile(profile: JSSelfProfile): profile is JSSelfProfile & { pr } if (!profile.frames.length) { - if(__DEBUG_BUILD__) { + if (__DEBUG_BUILD__) { logger.log('[Profiling] Discarding profile because it contains no frames'); } return false; From d93d94c2ea2777a356c41101099f8c556563d6ab Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 23 Jun 2023 13:03:19 -0400 Subject: [PATCH 10/11] fix: remove profilesSampler --- .../browser/src/profiling/hubextensions.ts | 20 ++----------------- packages/browser/src/profiling/integration.ts | 4 ++-- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 5b45b593b2e2..fe8e7c7e2aa0 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -71,20 +71,8 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact return transaction; } - // @ts-ignore profilesSampler is not part of the browser options yet - const profilesSampler = options.profilesSampler; // @ts-ignore profilesSampleRate is not part of the browser options yet - let profilesSampleRate: number | boolean | undefined = options.profilesSampleRate; - - // Prefer sampler to sample rate if both are provided. - if (typeof profilesSampler === 'function') { - const transactionContext = transaction.toContext(); - profilesSampleRate = profilesSampler({ - parentSampled: transactionContext.parentSampled, - transactionContext: transactionContext, - ...transaction.getDynamicSamplingContext(), - }); - } + const profilesSampleRate: number | boolean | undefined = options.profilesSampleRate; // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The // only valid values are booleans or numbers between 0 and 1.) @@ -97,11 +85,7 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact if (!profilesSampleRate) { __DEBUG_BUILD__ && logger.log( - `[Profiling] Discarding profile because ${ - typeof profilesSampler === 'function' - ? 'profileSampler returned 0 or false' - : 'a negative sampling decision was inherited or profileSampleRate is set to 0' - }`, + '[Profiling] Discarding profile because a negative sampling decision was inherited or profileSampleRate is set to 0', ); return transaction; } diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index eee2691bddc6..36fb6432e6df 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -61,8 +61,8 @@ export class BrowserProfilingIntegration implements Integration { } // Remove the profile from the transaction context before sending, relay will take care of the rest. - if (profiledTransaction && profiledTransaction.contexts && profiledTransaction.contexts['profile']) { - delete profiledTransaction.contexts.profile; + if (context && context['profile']) { + delete context.profile; } const profile = PROFILE_MAP.get(profile_id); From 63da4c43166a4f5f5c788cdada1f7311c2619876 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 23 Jun 2023 15:38:07 -0400 Subject: [PATCH 11/11] ref: call finish from onresolve and onreject to prevent double calls --- packages/browser/src/profiling/hubextensions.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index fe8e7c7e2aa0..1c04eeb68362 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -232,15 +232,16 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact } // onProfileHandler should always return the same profile even if this is called multiple times. // Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared. - void onProfileHandler() - .then(() => { + void onProfileHandler().then( + () => { transaction.setContext('profile', { profile_id: profileId }); originalFinish(); - }) - .catch(() => { + }, + () => { // If onProfileHandler fails, we still want to call the original finish method. originalFinish(); - }); + }, + ); return transaction; }