From c609f0b5db2ab2ce5504101dffdf5a2296a30f06 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Sat, 14 Aug 2021 15:58:15 +0700 Subject: [PATCH 01/14] feat: Add initWith method + reactnativeprofiler --- .gitignore | 4 ++ sample/src/App.tsx | 61 +++++++++++--------------- src/js/index.ts | 14 +++++- src/js/options.ts | 10 +++++ src/js/{sdk.ts => sdk.tsx} | 54 ++++++++++++++++++++--- src/js/tracing/index.ts | 1 + src/js/tracing/reactnativeprofiler.tsx | 26 +++++++++++ src/js/tracing/reactnativetracing.ts | 18 ++++++-- 8 files changed, 143 insertions(+), 45 deletions(-) rename src/js/{sdk.ts => sdk.tsx} (77%) create mode 100644 src/js/tracing/reactnativeprofiler.tsx diff --git a/.gitignore b/.gitignore index c843fcfb21..aad533aa28 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,7 @@ wheelhouse # Android .idea/ + +# Yalc +.yalc +yalc.lock diff --git a/sample/src/App.tsx b/sample/src/App.tsx index 3ff246f63f..84e1fb4420 100644 --- a/sample/src/App.tsx +++ b/sample/src/App.tsx @@ -25,8 +25,7 @@ const reactNavigationV5Instrumentation = new Sentry.ReactNavigationV5Instrumenta routeChangeTimeoutMs: 500, // How long it will wait for the route change to complete. Default is 1000ms }, ); - -Sentry.init({ +const options = { // Replace the example DSN below with your own DSN: dsn: SENTRY_INTERNAL_DSN, debug: true, @@ -63,7 +62,9 @@ Sentry.init({ // otherwise they will not work. release: packageVersion, dist: `${packageVersion}.0`, -}); +}; + +// Sentry.init(options); const Stack = createStackNavigator(); @@ -71,38 +72,28 @@ const App = () => { const navigation = React.useRef(); return ( - - - { - reactNavigationV5Instrumentation.registerNavigationContainer( - navigation, - ); - }}> - - - - - - - - - - - + + { + reactNavigationV5Instrumentation.registerNavigationContainer( + navigation, + ); + }}> + + + + + + + + + + ); }; -export default Sentry.withTouchEventBoundary(App, { - ignoreNames: ['Provider', 'UselessName', /^SomeRegex/], -}); +export default Sentry.initWith(App, options); diff --git a/src/js/index.ts b/src/js/index.ts index 8a9bd9dd8b..2ee4658ce4 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -59,8 +59,18 @@ import { SDK_NAME, SDK_VERSION } from "./version"; export { ReactNativeBackend } from "./backend"; export { ReactNativeOptions } from "./options"; export { ReactNativeClient } from "./client"; -// eslint-disable-next-line deprecation/deprecation -export { init, setDist, setRelease, nativeCrash, flush, close } from "./sdk"; + +export { + init, + initWith, + // eslint-disable-next-line deprecation/deprecation + setDist, + // eslint-disable-next-line deprecation/deprecation + setRelease, + nativeCrash, + flush, + close, +} from "./sdk"; export { TouchEventBoundary, withTouchEventBoundary } from "./touchevents"; export { diff --git a/src/js/options.ts b/src/js/options.ts index 149ba8070e..e94e89e69e 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -1,4 +1,8 @@ import { BrowserOptions } from "@sentry/react"; +import { ErrorBoundaryProps } from "@sentry/react/dist/errorboundary"; +import { ProfilerProps } from "@sentry/react/dist/profiler"; + +import { TouchEventBoundaryProps } from "./touchevents"; /** * Configuration options for the Sentry ReactNative SDK. @@ -72,3 +76,9 @@ export interface ReactNativeOptions extends BrowserOptions { /** Enable auto performance tracking by default. */ enableAutoPerformanceTracking?: boolean; } + +export interface ReactNativeWrapperOptions extends ReactNativeOptions { + errorBoundaryProps?: ErrorBoundaryProps; + profilerProps?: ProfilerProps; + touchEventBoundaryProps?: TouchEventBoundaryProps; +} diff --git a/src/js/sdk.ts b/src/js/sdk.tsx similarity index 77% rename from src/js/sdk.ts rename to src/js/sdk.tsx index ad89d719df..f367284a73 100644 --- a/src/js/sdk.ts +++ b/src/js/sdk.tsx @@ -1,9 +1,14 @@ import { initAndBind, setExtra } from "@sentry/core"; import { Hub, makeMain } from "@sentry/hub"; import { RewriteFrames } from "@sentry/integrations"; -import { defaultIntegrations, getCurrentHub } from "@sentry/react"; +import { + defaultIntegrations, + ErrorBoundary, + getCurrentHub, +} from "@sentry/react"; import { StackFrame } from "@sentry/types"; import { getGlobalObject, logger } from "@sentry/utils"; +import * as React from "react"; import { ReactNativeClient } from "./client"; import { @@ -13,9 +18,10 @@ import { Release, StallTracking, } from "./integrations"; -import { ReactNativeOptions } from "./options"; +import { ReactNativeOptions, ReactNativeWrapperOptions } from "./options"; import { ReactNativeScope } from "./scope"; -import { ReactNativeTracing } from "./tracing"; +import { TouchEventBoundary } from "./touchevents"; +import { ReactNativeProfiler, ReactNativeTracing } from "./tracing"; const IGNORED_DEFAULT_INTEGRATIONS = [ "GlobalHandlers", // We will use the react-native internal handlers @@ -31,9 +37,9 @@ const DEFAULT_OPTIONS: ReactNativeOptions = { }; /** - * Inits the SDK + * Inits the SDK and returns the final options. */ -export function init(passedOptions: ReactNativeOptions): void { +function _init(passedOptions: O): O { const reactNativeHub = new Hub(undefined, new ReactNativeScope()); makeMain(reactNativeHub); @@ -106,6 +112,44 @@ export function init(passedOptions: ReactNativeOptions): void { if (getGlobalObject().HermesInternal) { getCurrentHub().setTag("hermes", "true"); } + + return options; +} + +/** + * Inits the Sentry React Native SDK without any wrapping + */ +export function init(options: ReactNativeOptions): void { + _init(options); +} + +/** + * Inits the Sentry React Native SDK with automatic instrumentation and wrapped features. + */ +export function initWith( + RootComponent: React.ComponentType, + passedOptions: ReactNativeWrapperOptions +): React.FC { + const options = _init(passedOptions); + + const profilerProps = { + ...options.profilerProps, + name: RootComponent.displayName ?? "Root", + }; + + const RootApp: React.FC = (appProps) => { + return ( + + + + + + + + ); + }; + + return RootApp; } /** diff --git a/src/js/tracing/index.ts b/src/js/tracing/index.ts index 09ea506d3d..f3b15948ce 100644 --- a/src/js/tracing/index.ts +++ b/src/js/tracing/index.ts @@ -12,3 +12,4 @@ export { ReactNavigationRoute, ReactNavigationTransactionContext, } from "./types"; +export { ReactNativeProfiler } from "./reactnativeprofiler"; diff --git a/src/js/tracing/reactnativeprofiler.tsx b/src/js/tracing/reactnativeprofiler.tsx new file mode 100644 index 0000000000..018e43cc03 --- /dev/null +++ b/src/js/tracing/reactnativeprofiler.tsx @@ -0,0 +1,26 @@ +import { getCurrentHub, Profiler } from "@sentry/react"; + +import { ReactNativeTracing } from "./reactnativetracing"; + +/** + * Custom profiler for the React Native app root. + */ +export class ReactNativeProfiler extends Profiler { + /** + * Get the app root mount time. + */ + public componentDidMount(): void { + super.componentDidMount(); + + const tracingIntegration = getCurrentHub().getIntegration( + ReactNativeTracing + ); + + if (this._mountSpan && tracingIntegration) { + if (typeof this._mountSpan.endTimestamp !== "undefined") { + // The first root component mount is the app start finish. + tracingIntegration.onAppStartFinish(this._mountSpan.endTimestamp); + } + } + } +} diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index e396c13d6c..c4f39106e5 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -115,6 +115,7 @@ export class ReactNativeTracing implements Integration { private _getCurrentHub?: () => Hub; private _awaitingAppStartData?: NativeAppStartResponse; + private _appStartFinishTimestamp?: number; public constructor(options: Partial = {}) { this.options = { @@ -196,6 +197,13 @@ export class ReactNativeTracing implements Integration { this.nativeFramesInstrumentation?.onTransactionFinish(transaction); } + /** + * + */ + public onAppStartFinish(endTimestamp: number): void { + this._appStartFinishTimestamp = endTimestamp; + } + /** * Instruments the app start measurements on the first route transaction. * Starts a route transaction if there isn't routing instrumentation. @@ -235,18 +243,22 @@ export class ReactNativeTracing implements Integration { transaction: IdleTransaction, appStart: NativeAppStartResponse ): void { + if (!this._appStartFinishTimestamp) { + logger.warn("App start was never finished."); + return; + } + const appStartTimeSeconds = appStart.appStartTime / 1000; - const timeOriginSeconds = getTimeOriginMilliseconds() / 1000; transaction.startChild({ description: appStart.isColdStart ? "Cold App Start" : "Warm App Start", op: appStart.isColdStart ? "app.start.cold" : "app.start.warm", startTimestamp: appStartTimeSeconds, - endTimestamp: timeOriginSeconds, + endTimestamp: this._appStartFinishTimestamp, }); const appStartDurationMilliseconds = - getTimeOriginMilliseconds() - appStart.appStartTime; + this._appStartFinishTimestamp * 1000 - appStart.appStartTime; transaction.setMeasurements( appStart.isColdStart From 52368e1a88a1d428294d45fd2ecacde8b936cbe9 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Mon, 23 Aug 2021 15:00:15 -0500 Subject: [PATCH 02/14] Condense options and move stalltracking into tracing integration --- src/js/integrations/index.ts | 1 - src/js/measurements.ts | 38 +- src/js/options.ts | 3 - src/js/sdk.tsx | 5 - src/js/tracing/reactnativetracing.ts | 75 ++- .../stalltracking.ts | 46 +- src/js/tracing/utils.ts | 14 + test/integrations/stalltracking.test.ts | 444 ---------------- test/tracing/stalltracking.test.ts | 492 ++++++++++++++++++ 9 files changed, 565 insertions(+), 553 deletions(-) rename src/js/{integrations => tracing}/stalltracking.ts (93%) delete mode 100644 test/integrations/stalltracking.test.ts create mode 100644 test/tracing/stalltracking.test.ts diff --git a/src/js/integrations/index.ts b/src/js/integrations/index.ts index 51f0baee77..ba83c2f408 100644 --- a/src/js/integrations/index.ts +++ b/src/js/integrations/index.ts @@ -2,4 +2,3 @@ export { DebugSymbolicator } from "./debugsymbolicator"; export { DeviceContext } from "./devicecontext"; export { ReactNativeErrorHandlers } from "./reactnativeerrorhandlers"; export { Release } from "./release"; -export { StallTracking } from "./stalltracking"; diff --git a/src/js/measurements.ts b/src/js/measurements.ts index 5b20f135dc..7cb64dc4c7 100644 --- a/src/js/measurements.ts +++ b/src/js/measurements.ts @@ -1,9 +1,7 @@ import { getCurrentHub, getMainCarrier, Hub } from "@sentry/hub"; import { Transaction } from "@sentry/tracing"; import { CustomSamplingContext, TransactionContext } from "@sentry/types"; -import { logger } from "@sentry/utils"; -import { StallTracking } from "./integrations"; import { ReactNativeTracing } from "./tracing"; /** @@ -58,40 +56,20 @@ const _patchStartTransaction = ( const reactNativeTracing = getCurrentHub().getIntegration( ReactNativeTracing ); - const stallTracking = getCurrentHub().getIntegration(StallTracking); if (reactNativeTracing) { reactNativeTracing.onTransactionStart(transaction); - } - - if (stallTracking) { - if (!transactionContext.startTimestamp) { - const finishStallTracking = stallTracking.registerTransactionStart( - transaction - ); - - // eslint-disable-next-line @typescript-eslint/unbound-method - const originalFinish = transaction.finish; - - transaction.finish = (endTimestamp: number | undefined) => { - const stallMeasurements = finishStallTracking(); - // Sometimes the measurements will not be tracked due to some underlying reason, we don't add them in that case. - if (stallMeasurements) { - transaction.setMeasurements(stallMeasurements); - } + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalFinish = transaction.finish; - if (reactNativeTracing) { - reactNativeTracing.onTransactionFinish(transaction); - } + transaction.finish = (endTimestamp: number | undefined) => { + if (reactNativeTracing) { + reactNativeTracing.onTransactionFinish(transaction); + } - return originalFinish.apply(transaction, [endTimestamp]); - }; - } else { - logger.log( - "[StallTracking] Stalls will not be tracked due to `startTimestamp` being set." - ); - } + return originalFinish.apply(transaction, [endTimestamp]); + }; } return transaction; diff --git a/src/js/options.ts b/src/js/options.ts index e94e89e69e..c0a8a4fe02 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -70,9 +70,6 @@ export interface ReactNativeOptions extends BrowserOptions { didCallNativeInit: boolean; }) => void; - /** Enable JS event loop stall tracking. Enabled by default. */ - enableStallTracking?: boolean; - /** Enable auto performance tracking by default. */ enableAutoPerformanceTracking?: boolean; } diff --git a/src/js/sdk.tsx b/src/js/sdk.tsx index f367284a73..f1a2546b78 100644 --- a/src/js/sdk.tsx +++ b/src/js/sdk.tsx @@ -32,7 +32,6 @@ const DEFAULT_OPTIONS: ReactNativeOptions = { enableNativeCrashHandling: true, enableNativeNagger: true, autoInitializeNativeSdk: true, - enableStallTracking: true, enableAutoPerformanceTracking: true, }; @@ -95,10 +94,6 @@ function _init(passedOptions: O): O { if (tracingEnabled) { if (options.enableAutoPerformanceTracking) { options.defaultIntegrations.push(new ReactNativeTracing()); - - if (options.enableStallTracking) { - options.defaultIntegrations.push(new StallTracking()); - } } } } diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index c4f39106e5..cb77886e9d 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -17,11 +17,12 @@ import { import { logger } from "@sentry/utils"; import { NativeAppStartResponse } from "../definitions"; -import { StallTracking } from "../integrations"; +import { ReactNativeOptions } from "../options"; import { RoutingInstrumentationInstance } from "../tracing/routingInstrumentation"; import { NATIVE } from "../wrapper"; import { NativeFramesInstrumentation } from "./nativeframes"; -import { adjustTransactionDuration, getTimeOriginMilliseconds } from "./utils"; +import { StallTrackingInstrumentation } from "./stalltracking"; +import { adjustTransactionDuration, isNearToNow } from "./utils"; export type BeforeNavigate = ( context: TransactionContext @@ -70,19 +71,6 @@ export interface ReactNativeTracingOptions * @returns A (potentially) modified context object, with `sampled = false` if the transaction should be dropped. */ beforeNavigate: BeforeNavigate; - - /** - * Track the app start time by adding measurements to the first route transaction. If there is no routing instrumentation - * an app start transaction will be started. - * - * Default: true - */ - enableAppStartTracking: boolean; - - /** - * Track slow/frozen frames from the native layer and adds them as measurements to all transactions. - */ - enableNativeFramesTracking: boolean; } const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { @@ -112,10 +100,12 @@ export class ReactNativeTracing implements Integration { public options: ReactNativeTracingOptions; public nativeFramesInstrumentation?: NativeFramesInstrumentation; + public stallTrackingInstrumentation?: StallTrackingInstrumentation; private _getCurrentHub?: () => Hub; private _awaitingAppStartData?: NativeAppStartResponse; private _appStartFinishTimestamp?: number; + private _sentryOptions?: ReactNativeOptions; public constructor(options: Partial = {}) { this.options = { @@ -143,11 +133,12 @@ export class ReactNativeTracing implements Integration { enableNativeFramesTracking, } = this.options; + this._sentryOptions = getCurrentHub()?.getClient()?.getOptions(); this._getCurrentHub = getCurrentHub; void this._instrumentAppStart(); - if (enableNativeFramesTracking) { + if (this._sentryOptions?.enableAutoPerformanceTracking) { this.nativeFramesInstrumentation = new NativeFramesInstrumentation( addGlobalEventProcessor, () => { @@ -160,6 +151,7 @@ export class ReactNativeTracing implements Integration { return false; } ); + this.stallTrackingInstrumentation = new StallTrackingInstrumentation(); } else { NATIVE.disableNativeFramesTracking(); } @@ -187,14 +179,25 @@ export class ReactNativeTracing implements Integration { * To be called on a transaction start. Can have async methods */ public onTransactionStart(transaction: Transaction): void { - this.nativeFramesInstrumentation?.onTransactionStart(transaction); + if (isNearToNow(transaction.startTimestamp)) { + // Only if this method is called at or within margin of error to the start timestamp. + this.nativeFramesInstrumentation?.onTransactionStart(transaction); + this.stallTrackingInstrumentation?.onTransactionStart(transaction); + } } /** * To be called on a transaction finish. Cannot have async methods. */ - public onTransactionFinish(transaction: Transaction): void { + public onTransactionFinish( + transaction: Transaction, + endTimestamp?: number + ): void { this.nativeFramesInstrumentation?.onTransactionFinish(transaction); + this.stallTrackingInstrumentation?.onTransactionFinish( + transaction, + endTimestamp + ); } /** @@ -209,7 +212,10 @@ export class ReactNativeTracing implements Integration { * Starts a route transaction if there isn't routing instrumentation. */ private async _instrumentAppStart(): Promise { - if (!this.options.enableAppStartTracking || !NATIVE.enableNative) { + if ( + !this._sentryOptions?.enableAutoPerformanceTracking || + !NATIVE.enableNative + ) { return; } @@ -316,12 +322,17 @@ export class ReactNativeTracing implements Integration { `[ReactNativeTracing] Starting ${context.op} transaction "${context.name}" on scope` ); - idleTransaction.registerBeforeFinishCallback((transaction) => { - this.onTransactionFinish(transaction); - }); + idleTransaction.registerBeforeFinishCallback( + (transaction, endTimestamp) => { + this.onTransactionFinish(transaction, endTimestamp); + } + ); idleTransaction.registerBeforeFinishCallback((transaction) => { - if (this.options.enableAppStartTracking && this._awaitingAppStartData) { + if ( + this._sentryOptions?.enableAutoPerformanceTracking && + this._awaitingAppStartData + ) { transaction.startTimestamp = this._awaitingAppStartData.appStartTime / 1000; transaction.op = "ui.load"; @@ -332,24 +343,6 @@ export class ReactNativeTracing implements Integration { } }); - const stallTracking = this._getCurrentHub().getIntegration(StallTracking); - - if (stallTracking) { - const stallTrackingFinish = stallTracking.registerTransactionStart( - idleTransaction - ); - - idleTransaction.registerBeforeFinishCallback( - (transaction, endTimestamp) => { - const stallMeasurements = stallTrackingFinish(endTimestamp); - - if (stallMeasurements) { - transaction.setMeasurements(stallMeasurements); - } - } - ); - } - idleTransaction.registerBeforeFinishCallback( (transaction, endTimestamp) => { adjustTransactionDuration( diff --git a/src/js/integrations/stalltracking.ts b/src/js/tracing/stalltracking.ts similarity index 93% rename from src/js/integrations/stalltracking.ts rename to src/js/tracing/stalltracking.ts index 5e8c07dcf8..889a575ea2 100644 --- a/src/js/integrations/stalltracking.ts +++ b/src/js/tracing/stalltracking.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import { IdleTransaction, Span, Transaction } from "@sentry/tracing"; -import { Integration, Measurements } from "@sentry/types"; +import { Measurements } from "@sentry/types"; import { logger, timestampInSeconds } from "@sentry/utils"; export interface StallMeasurements extends Measurements { @@ -31,17 +31,7 @@ const MAX_RUNNING_TRANSACTIONS = 10; * However, we modified the interval implementation to instead have a fixed loop timeout interval of `LOOP_TIMEOUT_INTERVAL_MS`. * We then would consider that iteration a stall when the total time for that interval to run is greater than `LOOP_TIMEOUT_INTERVAL_MS + minimumStallThreshold` */ -export class StallTracking implements Integration { - /** - * @inheritDoc - */ - public static id: string = "StallTracking"; - - /** - * @inheritDoc - */ - public name: string = StallTracking.id; - +export class StallTrackingInstrumentation { public isTracking: boolean = false; private _minimumStallThreshold: number; @@ -59,6 +49,7 @@ export class StallTracking implements Integration { Transaction, { longestStallTime: number; + atStart: StallMeasurements; atTimestamp: { timestamp: number; stats: StallMeasurements; @@ -84,21 +75,20 @@ export class StallTracking implements Integration { * Register a transaction as started. Starts stall tracking if not already running. * @returns A finish method that returns the stall measurements. */ - public registerTransactionStart( - transaction: Transaction - ): (endTimestamp?: number) => StallMeasurements | null { + public onTransactionStart(transaction: Transaction): void { if (this._statsByTransaction.has(transaction)) { logger.error( "[StallTracking] Tried to start stall tracking on a transaction already being tracked. Measurements might be lost." ); - return () => null; + return; } this._startTracking(); this._statsByTransaction.set(transaction, { longestStallTime: 0, atTimestamp: null, + atStart: this._getCurrentStats(transaction), }); this._flushLeakedTransactions(); @@ -123,11 +113,6 @@ export class StallTracking implements Integration { }; }; } - - const statsOnStart = this._getCurrentStats(transaction); - - return (endTimestamp?: number) => - this._onFinish(transaction, statsOnStart, endTimestamp); } /** @@ -135,11 +120,10 @@ export class StallTracking implements Integration { * Stops stall tracking if no more transactions are running. * @returns The stall measurements */ - private _onFinish( + public onTransactionFinish( transaction: Transaction | IdleTransaction, - statsOnStart: StallMeasurements, passedEndTimestamp?: number - ): StallMeasurements | null { + ): void { const transactionStats = this._statsByTransaction.get(transaction); if (!transactionStats) { @@ -151,7 +135,7 @@ export class StallTracking implements Integration { this._statsByTransaction.delete(transaction); this._shouldStopTracking(); - return null; + return; } const endTimestamp = passedEndTimestamp ?? transaction.endTimestamp; @@ -224,20 +208,24 @@ export class StallTracking implements Integration { ); } - return null; + return; } - return { + const measurements = { stall_count: { - value: statsOnFinish.stall_count.value - statsOnStart.stall_count.value, + value: + statsOnFinish.stall_count.value - + transactionStats.atStart.stall_count.value, }, stall_total_time: { value: statsOnFinish.stall_total_time.value - - statsOnStart.stall_total_time.value, + transactionStats.atStart.stall_total_time.value, }, stall_longest_time: statsOnFinish.stall_longest_time, }; + + transaction.setMeasurements(measurements); } /** diff --git a/src/js/tracing/utils.ts b/src/js/tracing/utils.ts index ce48886879..9c2fff755d 100644 --- a/src/js/tracing/utils.ts +++ b/src/js/tracing/utils.ts @@ -4,6 +4,13 @@ import { SpanStatus, Transaction, } from "@sentry/tracing"; +import { timestampInSeconds } from "@sentry/utils"; + +/** + * A margin of error of 50ms is allowed for the async native bridge call. + * Anything larger would reduce the accuracy of our frames measurements. + */ +export const MARGIN_OF_ERROR_SECONDS = 0.05; const timeOriginMilliseconds = Date.now(); @@ -64,3 +71,10 @@ export function instrumentChildSpanFinish( }; } } + +/** + * Determines if the timestamp is now or within the specified margin of error from now. + */ +export function isNearToNow(timestamp: number): boolean { + return Math.abs(timestampInSeconds() - timestamp) <= MARGIN_OF_ERROR_SECONDS; +} diff --git a/test/integrations/stalltracking.test.ts b/test/integrations/stalltracking.test.ts deleted file mode 100644 index 8316d61090..0000000000 --- a/test/integrations/stalltracking.test.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { IdleTransaction, Transaction } from "@sentry/tracing"; - -import { StallTracking } from "../../src/js/integrations"; - -const expensiveOperation = () => { - const expensiveObject: { value: string[] } = { - value: Array(100000).fill("expensive"), - }; - - // This works in sync, so it should stall the js event loop - for (let i = 0; i < 50; i++) { - JSON.parse(JSON.stringify(expensiveObject)); - } -}; - -describe("StallTracking", () => { - it("Stall tracking detects a JS stall", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - expensiveOperation(); - - setTimeout(() => { - const measurements = finishTracking(); - - expect(measurements).not.toEqual(null); - if (measurements !== null) { - expect(measurements.stall_count.value).toBeGreaterThan(0); - expect(measurements.stall_longest_time.value).toBeGreaterThan(0); - expect(measurements.stall_total_time.value).toBeGreaterThan(0); - } - - done(); - }, 500); - }); - - it("Stall tracking detects multiple JS stalls", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - expensiveOperation(); - - setTimeout(() => { - expensiveOperation(); - }, 200); - - setTimeout(() => { - const measurements = finishTracking(); - - expect(measurements).not.toEqual(null); - if (measurements !== null) { - expect(measurements.stall_count.value).toBe(2); - expect(measurements.stall_longest_time.value).toBeGreaterThan(0); - expect(measurements.stall_total_time.value).toBeGreaterThan(0); - } - - done(); - }, 500); - }); - - it("Stall tracking timeout is stopped after finishing all transactions (single)", () => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const measurements = finishTracking(); - - expect(measurements).not.toBe(null); - - expect(stallTracking.isTracking).toBe(false); - }); - - it("Stall tracking timeout is stopped after finishing all transactions (multiple)", (done) => { - const stallTracking = new StallTracking(); - - const transaction0 = new Transaction({ - name: "Test Transaction 0", - }); - const transaction1 = new Transaction({ - name: "Test Transaction 1", - }); - const transaction2 = new Transaction({ - name: "Test Transaction 2", - }); - - const finishTracking0 = stallTracking.registerTransactionStart( - transaction0 - ); - const finishTracking1 = stallTracking.registerTransactionStart( - transaction1 - ); - - const measurements0 = finishTracking0(); - expect(measurements0).not.toBe(null); - - setTimeout(() => { - const measurements1 = finishTracking1(); - expect(measurements1).not.toBe(null); - }, 600); - - setTimeout(() => { - const finishTracking2 = stallTracking.registerTransactionStart( - transaction2 - ); - - setTimeout(() => { - const measurements2 = finishTracking2(); - expect(measurements2).not.toBe(null); - - expect(stallTracking.isTracking).toBe(false); - - done(); - }, 200); - }, 500); - - // If the stall tracking does not correctly stop, the process will keep running. We detect this by passing --detectOpenHandles to jest. - }); - - it("Stall tracking returns measurements format on finish", () => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const measurements = finishTracking(); - - expect(measurements).not.toBe(null); - - if (measurements !== null) { - expect(measurements.stall_count.value).toBe(0); - expect(measurements.stall_longest_time.value).toBe(0); - expect(measurements.stall_total_time.value).toBe(0); - } - }); - - it("Stall tracking only tracks a transaction once", () => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const noopFinishTracking = stallTracking.registerTransactionStart( - transaction - ); - - const measurements = finishTracking(); - - expect(measurements).not.toBe(null); - - expect(noopFinishTracking()).toBe(null); - }); - - it("Stall tracking returns null on a custom endTimestamp that is not a span's", () => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const measurements = finishTracking(Date.now() / 1000); - - expect(measurements).toBe(null); - }); - - it("Stall tracking supports endTimestamp that is from the last span (trimEnd case)", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - trimEnd: true, - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const span = transaction.startChild({ - description: "Test Span", - }); - - let spanFinishTime: number | undefined; - - setTimeout(() => { - spanFinishTime = Date.now() / 1000; - - span.finish(spanFinishTime); - }, 100); - - setTimeout(() => { - expect(spanFinishTime).toEqual(expect.any(Number)); - - const measurements = finishTracking(spanFinishTime); - - expect(measurements).not.toEqual(null); - - if (measurements !== null) { - expect(measurements.stall_count.value).toEqual(expect.any(Number)); - expect(measurements.stall_longest_time.value).toEqual( - expect.any(Number) - ); - expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - } - - done(); - }, 400); - }); - - it("Stall tracking rejects endTimestamp that is from the last span if trimEnd is false (trimEnd case)", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - trimEnd: false, - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const span = transaction.startChild({ - description: "Test Span", - }); - - let spanFinishTime: number | undefined; - - setTimeout(() => { - spanFinishTime = Date.now() / 1000; - - span.finish(spanFinishTime); - }, 100); - - setTimeout(() => { - expect(spanFinishTime).toEqual(expect.any(Number)); - - const measurements = finishTracking(spanFinishTime); - - expect(measurements).toBe(null); - - done(); - }, 400); - }); - - it("Stall tracking rejects endTimestamp even if it is a span time (custom endTimestamp case)", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - const span = transaction.startChild({ - description: "Test Span", - }); - - let spanFinishTime: number | undefined; - - setTimeout(() => { - spanFinishTime = Date.now() / 1000; - - span.finish(spanFinishTime); - }, 100); - - setTimeout(() => { - expect(spanFinishTime).toEqual(expect.any(Number)); - - if (typeof spanFinishTime === "number") { - const measurements = finishTracking(spanFinishTime + 0.015); - - expect(measurements).toBe(null); - } - - done(); - }, 400); - }); - - it("Stall tracking supports idleTransaction with unfinished spans", (done) => { - const stallTracking = new StallTracking(); - - const idleTransaction = new IdleTransaction({ - name: "Test Transaction", - trimEnd: true, - }); - idleTransaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart( - idleTransaction - ); - - idleTransaction.registerBeforeFinishCallback((_, endTimestamp) => { - const measurements = finishTracking(endTimestamp); - - expect(measurements).not.toEqual(null); - - if (measurements !== null) { - expect(measurements.stall_count.value).toEqual(expect.any(Number)); - expect(measurements.stall_longest_time.value).toEqual( - expect.any(Number) - ); - expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - } - - done(); - }); - - // Span is never finished. - idleTransaction.startChild({ - description: "Test Span", - }); - - setTimeout(() => { - idleTransaction.finish(); - }, 100); - }); - - it("Stall tracking ignores unfinished spans in normal transactions", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - trimEnd: true, - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - // Span is never finished. - transaction.startChild({ - description: "Test Span", - }); - - // Span will be finished - const span = transaction.startChild({ - description: "To Finish", - }); - - setTimeout(() => { - span.finish(); - }, 100); - - setTimeout(() => { - const measurements = finishTracking(); - - expect(measurements).not.toEqual(null); - - if (measurements !== null) { - expect(measurements.stall_count.value).toEqual(expect.any(Number)); - expect(measurements.stall_longest_time.value).toEqual( - expect.any(Number) - ); - expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - } - - done(); - }, 500); - }); - - it("Stall tracking only measures stalls inside the final time when trimEnd is used", (done) => { - const stallTracking = new StallTracking(); - - const transaction = new Transaction({ - name: "Test Transaction", - trimEnd: true, - }); - transaction.initSpanRecorder(); - - const finishTracking = stallTracking.registerTransactionStart(transaction); - - // Span will be finished - const span = transaction.startChild({ - description: "To Finish", - }); - - setTimeout(() => { - span.finish(); - }, 200); - - setTimeout(() => { - const measurements = finishTracking(); - - expect(measurements).not.toEqual(null); - - if (measurements !== null) { - expect(measurements.stall_count.value).toEqual(1); - expect(measurements.stall_longest_time.value).toEqual( - expect.any(Number) - ); - expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - } - - done(); - }, 500); - - setTimeout(() => { - // this should be run after the span finishes, and not logged. - expensiveOperation(); - }, 300); - - expensiveOperation(); - }); - - it("Stall tracking discards the first transaction if more than 10 are running", () => { - const stallTracking = new StallTracking(); - - const transactionFinishes = new Array(11).fill(0).map((_, i) => { - const transaction = new Transaction({ - name: `Test Transaction ${i}`, - }); - - return stallTracking.registerTransactionStart(transaction); - }); - - const measurements0 = transactionFinishes[0](); - expect(measurements0).toBe(null); - - const measurements1 = transactionFinishes[1](); - expect(measurements1).not.toBe(null); - - transactionFinishes.slice(2).forEach((finish) => finish()); - }); -}); diff --git a/test/tracing/stalltracking.test.ts b/test/tracing/stalltracking.test.ts new file mode 100644 index 0000000000..080e8cfe03 --- /dev/null +++ b/test/tracing/stalltracking.test.ts @@ -0,0 +1,492 @@ +import { IdleTransaction, Transaction } from "@sentry/tracing"; +import { Event } from "@sentry/types"; + +import { StallTrackingInstrumentation } from "../../src/js/tracing/stalltracking"; + +const hub = { + captureEvent: jest.fn(), +}; + +jest.mock("@sentry/hub", () => { + const hubOriginal = jest.requireActual("@sentry/hub"); + + return { + ...hubOriginal, + getCurrentHub: () => hub, + }; +}); + +const getLastEvent = (): Event => { + return hub.captureEvent.mock.calls[hub.captureEvent.mock.calls.length - 1][0]; +}; + +const expensiveOperation = () => { + const expensiveObject: { value: string[] } = { + value: Array(100000).fill("expensive"), + }; + + // This works in sync, so it should stall the js event loop + for (let i = 0; i < 50; i++) { + JSON.parse(JSON.stringify(expensiveObject)); + } +}; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe("StallTracking", () => { + // it("Stall tracking detects a JS stall", (done) => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // sampled: true, + // }); + // transaction.initSpanRecorder(); + + // stallTracking.onTransactionStart(transaction); + + // expensiveOperation(); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeDefined(); + // if (measurements) { + // expect(measurements.stall_count.value).toBeGreaterThan(0); + // expect(measurements.stall_longest_time.value).toBeGreaterThan(0); + // expect(measurements.stall_total_time.value).toBeGreaterThan(0); + // } + + // done(); + // }, 500); + // }); + + // it("Stall tracking detects multiple JS stalls", (done) => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // sampled: true, + // }); + // transaction.initSpanRecorder(); + + // stallTracking.onTransactionStart(transaction); + + // expensiveOperation(); + + // setTimeout(() => { + // expensiveOperation(); + // }, 200); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeDefined(); + // if (measurements) { + // expect(measurements.stall_count.value).toBe(2); + // expect(measurements.stall_longest_time.value).toBeGreaterThan(0); + // expect(measurements.stall_total_time.value).toBeGreaterThan(0); + // } + + // done(); + // }, 500); + // }); + + // it("Stall tracking timeout is stopped after finishing all transactions (single)", () => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // sampled: true, + // }); + + // stallTracking.onTransactionStart(transaction); + + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).not.toBe(null); + + // expect(stallTracking.isTracking).toBe(false); + // }); + + // it("Stall tracking timeout is stopped after finishing all transactions (multiple)", (done) => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction0 = new Transaction({ + // name: "Test Transaction 0", + // sampled: true, + // }); + // const transaction1 = new Transaction({ + // name: "Test Transaction 1", + // sampled: true, + // }); + // const transaction2 = new Transaction({ + // name: "Test Transaction 2", + // sampled: true, + // }); + + // stallTracking.onTransactionStart(transaction0); + // stallTracking.onTransactionStart(transaction1); + + // stallTracking.onTransactionFinish(transaction0); + // transaction0.finish(); + // const measurements0 = getLastEvent()?.measurements; + // expect(measurements0).toBeDefined(); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction1); + // transaction1.finish(); + // const measurements1 = getLastEvent()?.measurements; + // expect(measurements1).toBeDefined(); + // }, 600); + + // setTimeout(() => { + // stallTracking.onTransactionStart(transaction2); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction2); + // transaction2.finish(); + // const measurements2 = getLastEvent()?.measurements; + // expect(measurements2).not.toBe(null); + + // expect(stallTracking.isTracking).toBe(false); + + // done(); + // }, 200); + // }, 500); + + // // If the stall tracking does not correctly stop, the process will keep running. We detect this by passing --detectOpenHandles to jest. + // }); + + // it("Stall tracking returns measurements format on finish", () => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // sampled: true, + // }); + + // stallTracking.onTransactionStart(transaction); + + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeDefined(); + + // if (measurements) { + // expect(measurements.stall_count.value).toBe(0); + // expect(measurements.stall_longest_time.value).toBe(0); + // expect(measurements.stall_total_time.value).toBe(0); + // } + // }); + + // it("Stall tracking returns null on a custom endTimestamp that is not a span's", () => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // sampled: true, + // }); + + // stallTracking.onTransactionStart(transaction); + + // stallTracking.onTransactionFinish(transaction, Date.now() / 1000); + // transaction.finish(); + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeUndefined(); + // }); + + it("Stall tracking supports endTimestamp that is from the last span (trimEnd case)", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const transaction = new Transaction({ + name: "Test Transaction", + trimEnd: true, + sampled: true, + }); + transaction.initSpanRecorder(); + + stallTracking.onTransactionStart(transaction); + + const span = transaction.startChild({ + description: "Test Span", + }); + + let spanFinishTime: number | undefined; + + setTimeout(() => { + spanFinishTime = Date.now() / 1000; + + span.finish(spanFinishTime); + }, 100); + + setTimeout(() => { + expect(spanFinishTime).toEqual(expect.any(Number)); + + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeDefined(); + + if (measurements) { + expect(measurements.stall_count.value).toEqual(expect.any(Number)); + expect(measurements.stall_longest_time.value).toEqual( + expect.any(Number) + ); + expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + } + + done(); + }, 400); + }); + + it("Stall tracking rejects endTimestamp that is from the last span if trimEnd is false (trimEnd case)", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const transaction = new Transaction({ + name: "Test Transaction", + trimEnd: false, + sampled: true, + }); + transaction.initSpanRecorder(); + + stallTracking.onTransactionStart(transaction); + + const span = transaction.startChild({ + description: "Test Span", + }); + + let spanFinishTime: number | undefined; + + setTimeout(() => { + spanFinishTime = Date.now() / 1000; + + span.finish(spanFinishTime); + }, 100); + + setTimeout(() => { + expect(spanFinishTime).toEqual(expect.any(Number)); + + stallTracking.onTransactionFinish(transaction, spanFinishTime); + transaction.finish(); + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeUndefined(); + + done(); + }, 400); + }); + + it("Stall tracking rejects endTimestamp even if it is a span time (custom endTimestamp case)", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); + transaction.initSpanRecorder(); + + stallTracking.onTransactionStart(transaction); + + const span = transaction.startChild({ + description: "Test Span", + }); + + let spanFinishTime: number | undefined; + + setTimeout(() => { + spanFinishTime = Date.now() / 1000; + + span.finish(spanFinishTime); + }, 100); + + setTimeout(() => { + expect(spanFinishTime).toEqual(expect.any(Number)); + + if (typeof spanFinishTime === "number") { + stallTracking.onTransactionFinish(transaction, spanFinishTime + 0.015); + transaction.finish(); + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeUndefined(); + } + + done(); + }, 400); + }); + + it("Stall tracking supports idleTransaction with unfinished spans", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const idleTransaction = new IdleTransaction({ + name: "Test Transaction", + trimEnd: true, + sampled: true, + }); + idleTransaction.initSpanRecorder(); + + stallTracking.onTransactionStart(idleTransaction); + + idleTransaction.registerBeforeFinishCallback((_, endTimestamp) => { + stallTracking.onTransactionFinish(idleTransaction, endTimestamp); + }); + + // Span is never finished. + idleTransaction.startChild({ + description: "Test Span", + }); + + setTimeout(() => { + idleTransaction.finish(); + + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeDefined(); + + if (measurements) { + expect(measurements.stall_count.value).toEqual(expect.any(Number)); + expect(measurements.stall_longest_time.value).toEqual( + expect.any(Number) + ); + expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + } + + done(); + }, 100); + }); + + // it("Stall tracking ignores unfinished spans in normal transactions", (done) => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // trimEnd: true, + // sampled: true, + // }); + // transaction.initSpanRecorder(); + + // stallTracking.onTransactionStart(transaction); + + // // Span is never finished. + // transaction.startChild({ + // description: "Test Span", + // }); + + // // Span will be finished + // const span = transaction.startChild({ + // description: "To Finish", + // }); + + // setTimeout(() => { + // span.finish(); + // }, 100); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeDefined(); + + // if (measurements) { + // expect(measurements.stall_count.value).toEqual(expect.any(Number)); + // expect(measurements.stall_longest_time.value).toEqual( + // expect.any(Number) + // ); + // expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + // } + + // done(); + // }, 500); + // }); + + // it("Stall tracking only measures stalls inside the final time when trimEnd is used", (done) => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transaction = new Transaction({ + // name: "Test Transaction", + // trimEnd: true, + // sampled: true, + // }); + // transaction.initSpanRecorder(); + + // stallTracking.onTransactionStart(transaction); + + // // Span will be finished + // const span = transaction.startChild({ + // description: "To Finish", + // }); + + // setTimeout(() => { + // span.finish(); + // }, 200); + + // setTimeout(() => { + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + // const measurements = getLastEvent()?.measurements; + + // expect(measurements).toBeDefined(); + + // if (measurements) { + // expect(measurements.stall_count.value).toEqual(1); + // expect(measurements.stall_longest_time.value).toEqual( + // expect.any(Number) + // ); + // expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + // } + + // done(); + // }, 500); + + // setTimeout(() => { + // // this should be run after the span finishes, and not logged. + // expensiveOperation(); + // }, 300); + + // expensiveOperation(); + // }); + + // it("Stall tracking does not track the first transaction if more than 10 are running", () => { + // const stallTracking = new StallTrackingInstrumentation(); + + // const transactions = new Array(11).fill(0).map((_, i) => { + // const transaction = new Transaction({ + // name: `Test Transaction ${i}`, + // sampled: true, + // }); + + // stallTracking.onTransactionStart(transaction); + + // return transaction; + // }); + + // stallTracking.onTransactionFinish(transactions[0]); + // transactions[0].finish(); + // const measurements0 = getLastEvent()?.measurements; + // expect(measurements0).toBeUndefined(); + + // stallTracking.onTransactionFinish(transactions[1]); + // transactions[1].finish(); + // const measurements1 = getLastEvent()?.measurements; + // expect(measurements1).toBeDefined(); + + // transactions.slice(2).forEach((transaction) => { + // stallTracking.onTransactionFinish(transaction); + // transaction.finish(); + // }); + // }); +}); From 63e5a19beba72277128628df9f3a8514395dd57e Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Tue, 24 Aug 2021 11:41:21 -0500 Subject: [PATCH 03/14] feat: fallback app start and options method change --- src/js/sdk.tsx | 6 +- src/js/tracing/reactnativetracing.ts | 46 +++++--- test/sdk.test.ts | 136 ++++++------------------ test/tracing/reactnativetracing.test.ts | 1 + 4 files changed, 69 insertions(+), 120 deletions(-) diff --git a/src/js/sdk.tsx b/src/js/sdk.tsx index f1a2546b78..0c4bff1a21 100644 --- a/src/js/sdk.tsx +++ b/src/js/sdk.tsx @@ -16,7 +16,6 @@ import { DeviceContext, ReactNativeErrorHandlers, Release, - StallTracking, } from "./integrations"; import { ReactNativeOptions, ReactNativeWrapperOptions } from "./options"; import { ReactNativeScope } from "./scope"; @@ -127,6 +126,11 @@ export function initWith( ): React.FC { const options = _init(passedOptions); + const tracingIntegration = getCurrentHub().getIntegration(ReactNativeTracing); + if (tracingIntegration) { + tracingIntegration.useAppStartWithProfiler = true; + } + const profilerProps = { ...options.profilerProps, name: RootComponent.displayName ?? "Root", diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index cb77886e9d..ed2aa8a015 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -17,12 +17,15 @@ import { import { logger } from "@sentry/utils"; import { NativeAppStartResponse } from "../definitions"; -import { ReactNativeOptions } from "../options"; import { RoutingInstrumentationInstance } from "../tracing/routingInstrumentation"; import { NATIVE } from "../wrapper"; import { NativeFramesInstrumentation } from "./nativeframes"; import { StallTrackingInstrumentation } from "./stalltracking"; -import { adjustTransactionDuration, isNearToNow } from "./utils"; +import { + adjustTransactionDuration, + getTimeOriginMilliseconds, + isNearToNow, +} from "./utils"; export type BeforeNavigate = ( context: TransactionContext @@ -71,6 +74,19 @@ export interface ReactNativeTracingOptions * @returns A (potentially) modified context object, with `sampled = false` if the transaction should be dropped. */ beforeNavigate: BeforeNavigate; + + /** + * Track the app start time by adding measurements to the first route transaction. If there is no routing instrumentation + * an app start transaction will be started. + * + * Default: true + */ + enableAppStartTracking: boolean; + + /** + * Track slow/frozen frames from the native layer and adds them as measurements to all transactions. + */ + enableNativeFramesTracking: boolean; } const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { @@ -101,11 +117,11 @@ export class ReactNativeTracing implements Integration { public nativeFramesInstrumentation?: NativeFramesInstrumentation; public stallTrackingInstrumentation?: StallTrackingInstrumentation; + public useAppStartWithProfiler: boolean = false; private _getCurrentHub?: () => Hub; private _awaitingAppStartData?: NativeAppStartResponse; private _appStartFinishTimestamp?: number; - private _sentryOptions?: ReactNativeOptions; public constructor(options: Partial = {}) { this.options = { @@ -130,15 +146,17 @@ export class ReactNativeTracing implements Integration { // @ts-ignore TODO shouldCreateSpanForRequest, routingInstrumentation, + enableAppStartTracking, enableNativeFramesTracking, } = this.options; - this._sentryOptions = getCurrentHub()?.getClient()?.getOptions(); this._getCurrentHub = getCurrentHub; - void this._instrumentAppStart(); + if (enableAppStartTracking) { + void this._instrumentAppStart(); + } - if (this._sentryOptions?.enableAutoPerformanceTracking) { + if (enableNativeFramesTracking) { this.nativeFramesInstrumentation = new NativeFramesInstrumentation( addGlobalEventProcessor, () => { @@ -201,7 +219,7 @@ export class ReactNativeTracing implements Integration { } /** - * + * Called by the ReactNativeProfiler component on first component mount. */ public onAppStartFinish(endTimestamp: number): void { this._appStartFinishTimestamp = endTimestamp; @@ -212,10 +230,7 @@ export class ReactNativeTracing implements Integration { * Starts a route transaction if there isn't routing instrumentation. */ private async _instrumentAppStart(): Promise { - if ( - !this._sentryOptions?.enableAutoPerformanceTracking || - !NATIVE.enableNative - ) { + if (!this.options.enableAppStartTracking || !NATIVE.enableNative) { return; } @@ -225,6 +240,10 @@ export class ReactNativeTracing implements Integration { return; } + if (!this.useAppStartWithProfiler) { + this._appStartFinishTimestamp = getTimeOriginMilliseconds() / 1000; + } + if (this.options.routingInstrumentation) { this._awaitingAppStartData = appStart; } else { @@ -329,10 +348,7 @@ export class ReactNativeTracing implements Integration { ); idleTransaction.registerBeforeFinishCallback((transaction) => { - if ( - this._sentryOptions?.enableAutoPerformanceTracking && - this._awaitingAppStartData - ) { + if (this.options.enableAppStartTracking && this._awaitingAppStartData) { transaction.startTimestamp = this._awaitingAppStartData.appStartTime / 1000; transaction.op = "ui.load"; diff --git a/test/sdk.test.ts b/test/sdk.test.ts index 68739b246b..6b56ea383f 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -47,7 +47,6 @@ jest.spyOn(logger, "error"); import { initAndBind } from "@sentry/core"; import { getCurrentHub } from "@sentry/react"; -import { StallTracking } from "../src/js/integrations"; import { flush, init } from "../src/js/sdk"; import { ReactNativeTracing } from "../src/js/tracing"; @@ -57,8 +56,8 @@ afterEach(() => { describe("Tests the SDK functionality", () => { describe("init", () => { - describe("enableStallTracking", () => { - const stallTrackingIsEnabled = (): boolean => { + describe("enableAutoPerformanceTracking", () => { + const autoPerformanceIsEnabled = (): boolean => { const mockCall = (initAndBind as jest.MockedFunction< typeof initAndBind >).mock.calls[0]; @@ -68,7 +67,7 @@ describe("Tests the SDK functionality", () => { if (options.defaultIntegrations) { return options.defaultIntegrations?.some( - (integration) => integration.name === StallTracking.id + (integration) => integration.name === ReactNativeTracing.id ); } } @@ -76,129 +75,58 @@ describe("Tests the SDK functionality", () => { return false; }; - it("Stall Tracking is not enabled when tracing is disabled", () => { - init({ - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(false); - }); - - it("Stall Tracking is enabled when tracing is enabled (tracesSampler)", () => { + it("Auto Performance is enabled when tracing is enabled (tracesSampler)", () => { init({ tracesSampler: () => true, - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(true); - }); - - it("Stall Tracking is enabled when tracing is enabled (tracesSampleRate)", () => { - init({ - tracesSampleRate: 0.5, - enableStallTracking: true, - }); - - expect(stallTrackingIsEnabled()).toBe(true); - }); - - it("Stall Tracking is disabled when Auto performance tracking is disabled", () => { - init({ - tracesSampleRate: 0.5, - enableStallTracking: true, - enableAutoPerformanceTracking: false, + enableAutoPerformanceTracking: true, }); - expect(stallTrackingIsEnabled()).toBe(false); + expect(autoPerformanceIsEnabled()).toBe(true); }); - it("Stall Tracking is enabled when Auto performance tracking is enabled", () => { + it("Auto Performance is enabled when tracing is enabled (tracesSampleRate)", () => { init({ tracesSampleRate: 0.5, - enableStallTracking: true, enableAutoPerformanceTracking: true, }); - expect(stallTrackingIsEnabled()).toBe(true); + expect(autoPerformanceIsEnabled()).toBe(true); }); }); - }); - - describe("enableAutoPerformanceTracking", () => { - const autoPerformanceIsEnabled = (): boolean => { - const mockCall = (initAndBind as jest.MockedFunction) - .mock.calls[0]; - if (mockCall) { - const options = mockCall[1]; + describe("flush", () => { + it("Calls flush on the client", async () => { + const mockClient = getCurrentHub().getClient(); - if (options.defaultIntegrations) { - return options.defaultIntegrations?.some( - (integration) => integration.name === ReactNativeTracing.id - ); - } - } + expect(mockClient).toBeTruthy(); - return false; - }; + if (mockClient) { + const flushResult = await flush(); - it("Auto Performance is not enabled when tracing is disabled", () => { - init({ - enableStallTracking: true, + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(mockClient.flush).toBeCalled(); + expect(flushResult).toBe(true); + } }); - expect(autoPerformanceIsEnabled()).toBe(false); - }); + it("Returns false if flush failed and logs error", async () => { + const mockClient = getCurrentHub().getClient(); - it("Auto Performance is enabled when tracing is enabled (tracesSampler)", () => { - init({ - tracesSampler: () => true, - enableAutoPerformanceTracking: true, - }); + expect(mockClient).toBeTruthy(); + if (mockClient) { + mockClient.flush = jest.fn(() => Promise.reject()); - expect(autoPerformanceIsEnabled()).toBe(true); - }); + const flushResult = await flush(); - it("Auto Performance is enabled when tracing is enabled (tracesSampleRate)", () => { - init({ - tracesSampleRate: 0.5, - enableAutoPerformanceTracking: true, + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(mockClient.flush).toBeCalled(); + expect(flushResult).toBe(false); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(logger.error).toBeCalledWith( + "Failed to flush the event queue." + ); + } }); - - expect(autoPerformanceIsEnabled()).toBe(true); - }); - }); - - describe("flush", () => { - it("Calls flush on the client", async () => { - const mockClient = getCurrentHub().getClient(); - - expect(mockClient).toBeTruthy(); - - if (mockClient) { - const flushResult = await flush(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockClient.flush).toBeCalled(); - expect(flushResult).toBe(true); - } - }); - - it("Returns false if flush failed and logs error", async () => { - const mockClient = getCurrentHub().getClient(); - - expect(mockClient).toBeTruthy(); - if (mockClient) { - mockClient.flush = jest.fn(() => Promise.reject()); - - const flushResult = await flush(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockClient.flush).toBeCalled(); - expect(flushResult).toBe(false); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(logger.error).toBeCalledWith("Failed to flush the event queue."); - } }); }); }); diff --git a/test/tracing/reactnativetracing.test.ts b/test/tracing/reactnativetracing.test.ts index 1111e44682..0668f4ef7e 100644 --- a/test/tracing/reactnativetracing.test.ts +++ b/test/tracing/reactnativetracing.test.ts @@ -87,6 +87,7 @@ describe("ReactNativeTracing", () => { // use setImmediate as app start is handled inside a promise. setImmediate(() => { + integration.onAppStartFinish(Date.now() / 1000); const transaction = mockHub.getScope()?.getTransaction(); expect(transaction).toBeDefined(); From 4305934cae4c1764f70eeaa6e86e31e9c08e5a37 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Wed, 25 Aug 2021 18:59:11 -0500 Subject: [PATCH 04/14] fix: Revert stall tests --- test/tracing/stalltracking.test.ts | 500 ++++++++++++++--------------- 1 file changed, 250 insertions(+), 250 deletions(-) diff --git a/test/tracing/stalltracking.test.ts b/test/tracing/stalltracking.test.ts index 080e8cfe03..efd8bf9b07 100644 --- a/test/tracing/stalltracking.test.ts +++ b/test/tracing/stalltracking.test.ts @@ -36,177 +36,177 @@ beforeEach(() => { }); describe("StallTracking", () => { - // it("Stall tracking detects a JS stall", (done) => { - // const stallTracking = new StallTrackingInstrumentation(); + it("Stall tracking detects a JS stall", (done) => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction = new Transaction({ - // name: "Test Transaction", - // sampled: true, - // }); - // transaction.initSpanRecorder(); + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); + transaction.initSpanRecorder(); - // stallTracking.onTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - // expensiveOperation(); - - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); + expensiveOperation(); - // const measurements = getLastEvent()?.measurements; + setTimeout(() => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); - // expect(measurements).toBeDefined(); - // if (measurements) { - // expect(measurements.stall_count.value).toBeGreaterThan(0); - // expect(measurements.stall_longest_time.value).toBeGreaterThan(0); - // expect(measurements.stall_total_time.value).toBeGreaterThan(0); - // } + const measurements = getLastEvent()?.measurements; - // done(); - // }, 500); - // }); + expect(measurements).toBeDefined(); + if (measurements) { + expect(measurements.stall_count.value).toBeGreaterThan(0); + expect(measurements.stall_longest_time.value).toBeGreaterThan(0); + expect(measurements.stall_total_time.value).toBeGreaterThan(0); + } - // it("Stall tracking detects multiple JS stalls", (done) => { - // const stallTracking = new StallTrackingInstrumentation(); + done(); + }, 500); + }); + + it("Stall tracking detects multiple JS stalls", (done) => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction = new Transaction({ - // name: "Test Transaction", - // sampled: true, - // }); - // transaction.initSpanRecorder(); + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); + transaction.initSpanRecorder(); - // stallTracking.onTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - // expensiveOperation(); + expensiveOperation(); - // setTimeout(() => { - // expensiveOperation(); - // }, 200); + setTimeout(() => { + expensiveOperation(); + }, 200); - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); - // const measurements = getLastEvent()?.measurements; + setTimeout(() => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - // expect(measurements).toBeDefined(); - // if (measurements) { - // expect(measurements.stall_count.value).toBe(2); - // expect(measurements.stall_longest_time.value).toBeGreaterThan(0); - // expect(measurements.stall_total_time.value).toBeGreaterThan(0); - // } + expect(measurements).toBeDefined(); + if (measurements) { + expect(measurements.stall_count.value).toBe(2); + expect(measurements.stall_longest_time.value).toBeGreaterThan(0); + expect(measurements.stall_total_time.value).toBeGreaterThan(0); + } - // done(); - // }, 500); - // }); + done(); + }, 500); + }); - // it("Stall tracking timeout is stopped after finishing all transactions (single)", () => { - // const stallTracking = new StallTrackingInstrumentation(); + it("Stall tracking timeout is stopped after finishing all transactions (single)", () => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction = new Transaction({ - // name: "Test Transaction", - // sampled: true, - // }); + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); - // stallTracking.onTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); + stallTracking.onTransactionFinish(transaction); + transaction.finish(); - // const measurements = getLastEvent()?.measurements; + const measurements = getLastEvent()?.measurements; - // expect(measurements).not.toBe(null); + expect(measurements).not.toBe(null); - // expect(stallTracking.isTracking).toBe(false); - // }); + expect(stallTracking.isTracking).toBe(false); + }); - // it("Stall tracking timeout is stopped after finishing all transactions (multiple)", (done) => { - // const stallTracking = new StallTrackingInstrumentation(); + it("Stall tracking timeout is stopped after finishing all transactions (multiple)", (done) => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction0 = new Transaction({ - // name: "Test Transaction 0", - // sampled: true, - // }); - // const transaction1 = new Transaction({ - // name: "Test Transaction 1", - // sampled: true, - // }); - // const transaction2 = new Transaction({ - // name: "Test Transaction 2", - // sampled: true, - // }); + const transaction0 = new Transaction({ + name: "Test Transaction 0", + sampled: true, + }); + const transaction1 = new Transaction({ + name: "Test Transaction 1", + sampled: true, + }); + const transaction2 = new Transaction({ + name: "Test Transaction 2", + sampled: true, + }); - // stallTracking.onTransactionStart(transaction0); - // stallTracking.onTransactionStart(transaction1); + stallTracking.onTransactionStart(transaction0); + stallTracking.onTransactionStart(transaction1); - // stallTracking.onTransactionFinish(transaction0); - // transaction0.finish(); - // const measurements0 = getLastEvent()?.measurements; - // expect(measurements0).toBeDefined(); + stallTracking.onTransactionFinish(transaction0); + transaction0.finish(); + const measurements0 = getLastEvent()?.measurements; + expect(measurements0).toBeDefined(); - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction1); - // transaction1.finish(); - // const measurements1 = getLastEvent()?.measurements; - // expect(measurements1).toBeDefined(); - // }, 600); + setTimeout(() => { + stallTracking.onTransactionFinish(transaction1); + transaction1.finish(); + const measurements1 = getLastEvent()?.measurements; + expect(measurements1).toBeDefined(); + }, 600); - // setTimeout(() => { - // stallTracking.onTransactionStart(transaction2); + setTimeout(() => { + stallTracking.onTransactionStart(transaction2); - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction2); - // transaction2.finish(); - // const measurements2 = getLastEvent()?.measurements; - // expect(measurements2).not.toBe(null); + setTimeout(() => { + stallTracking.onTransactionFinish(transaction2); + transaction2.finish(); + const measurements2 = getLastEvent()?.measurements; + expect(measurements2).not.toBe(null); - // expect(stallTracking.isTracking).toBe(false); + expect(stallTracking.isTracking).toBe(false); - // done(); - // }, 200); - // }, 500); + done(); + }, 200); + }, 500); - // // If the stall tracking does not correctly stop, the process will keep running. We detect this by passing --detectOpenHandles to jest. - // }); + // If the stall tracking does not correctly stop, the process will keep running. We detect this by passing --detectOpenHandles to jest. + }); - // it("Stall tracking returns measurements format on finish", () => { - // const stallTracking = new StallTrackingInstrumentation(); + it("Stall tracking returns measurements format on finish", () => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction = new Transaction({ - // name: "Test Transaction", - // sampled: true, - // }); + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); - // stallTracking.onTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); - // const measurements = getLastEvent()?.measurements; + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - // expect(measurements).toBeDefined(); + expect(measurements).toBeDefined(); - // if (measurements) { - // expect(measurements.stall_count.value).toBe(0); - // expect(measurements.stall_longest_time.value).toBe(0); - // expect(measurements.stall_total_time.value).toBe(0); - // } - // }); + if (measurements) { + expect(measurements.stall_count.value).toBe(0); + expect(measurements.stall_longest_time.value).toBe(0); + expect(measurements.stall_total_time.value).toBe(0); + } + }); - // it("Stall tracking returns null on a custom endTimestamp that is not a span's", () => { - // const stallTracking = new StallTrackingInstrumentation(); + it("Stall tracking returns null on a custom endTimestamp that is not a span's", () => { + const stallTracking = new StallTrackingInstrumentation(); - // const transaction = new Transaction({ - // name: "Test Transaction", - // sampled: true, - // }); + const transaction = new Transaction({ + name: "Test Transaction", + sampled: true, + }); - // stallTracking.onTransactionStart(transaction); + stallTracking.onTransactionStart(transaction); - // stallTracking.onTransactionFinish(transaction, Date.now() / 1000); - // transaction.finish(); - // const measurements = getLastEvent()?.measurements; + stallTracking.onTransactionFinish(transaction, Date.now() / 1000); + transaction.finish(); + const measurements = getLastEvent()?.measurements; - // expect(measurements).toBeUndefined(); - // }); + expect(measurements).toBeUndefined(); + }); it("Stall tracking supports endTimestamp that is from the last span (trimEnd case)", (done) => { const stallTracking = new StallTrackingInstrumentation(); @@ -368,125 +368,125 @@ describe("StallTracking", () => { }, 100); }); - // it("Stall tracking ignores unfinished spans in normal transactions", (done) => { - // const stallTracking = new StallTrackingInstrumentation(); - - // const transaction = new Transaction({ - // name: "Test Transaction", - // trimEnd: true, - // sampled: true, - // }); - // transaction.initSpanRecorder(); - - // stallTracking.onTransactionStart(transaction); - - // // Span is never finished. - // transaction.startChild({ - // description: "Test Span", - // }); - - // // Span will be finished - // const span = transaction.startChild({ - // description: "To Finish", - // }); - - // setTimeout(() => { - // span.finish(); - // }, 100); - - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); - // const measurements = getLastEvent()?.measurements; - - // expect(measurements).toBeDefined(); - - // if (measurements) { - // expect(measurements.stall_count.value).toEqual(expect.any(Number)); - // expect(measurements.stall_longest_time.value).toEqual( - // expect.any(Number) - // ); - // expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - // } - - // done(); - // }, 500); - // }); - - // it("Stall tracking only measures stalls inside the final time when trimEnd is used", (done) => { - // const stallTracking = new StallTrackingInstrumentation(); - - // const transaction = new Transaction({ - // name: "Test Transaction", - // trimEnd: true, - // sampled: true, - // }); - // transaction.initSpanRecorder(); - - // stallTracking.onTransactionStart(transaction); - - // // Span will be finished - // const span = transaction.startChild({ - // description: "To Finish", - // }); - - // setTimeout(() => { - // span.finish(); - // }, 200); - - // setTimeout(() => { - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); - // const measurements = getLastEvent()?.measurements; - - // expect(measurements).toBeDefined(); - - // if (measurements) { - // expect(measurements.stall_count.value).toEqual(1); - // expect(measurements.stall_longest_time.value).toEqual( - // expect.any(Number) - // ); - // expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); - // } - - // done(); - // }, 500); - - // setTimeout(() => { - // // this should be run after the span finishes, and not logged. - // expensiveOperation(); - // }, 300); - - // expensiveOperation(); - // }); - - // it("Stall tracking does not track the first transaction if more than 10 are running", () => { - // const stallTracking = new StallTrackingInstrumentation(); - - // const transactions = new Array(11).fill(0).map((_, i) => { - // const transaction = new Transaction({ - // name: `Test Transaction ${i}`, - // sampled: true, - // }); - - // stallTracking.onTransactionStart(transaction); - - // return transaction; - // }); - - // stallTracking.onTransactionFinish(transactions[0]); - // transactions[0].finish(); - // const measurements0 = getLastEvent()?.measurements; - // expect(measurements0).toBeUndefined(); - - // stallTracking.onTransactionFinish(transactions[1]); - // transactions[1].finish(); - // const measurements1 = getLastEvent()?.measurements; - // expect(measurements1).toBeDefined(); - - // transactions.slice(2).forEach((transaction) => { - // stallTracking.onTransactionFinish(transaction); - // transaction.finish(); - // }); - // }); + it("Stall tracking ignores unfinished spans in normal transactions", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const transaction = new Transaction({ + name: "Test Transaction", + trimEnd: true, + sampled: true, + }); + transaction.initSpanRecorder(); + + stallTracking.onTransactionStart(transaction); + + // Span is never finished. + transaction.startChild({ + description: "Test Span", + }); + + // Span will be finished + const span = transaction.startChild({ + description: "To Finish", + }); + + setTimeout(() => { + span.finish(); + }, 100); + + setTimeout(() => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeDefined(); + + if (measurements) { + expect(measurements.stall_count.value).toEqual(expect.any(Number)); + expect(measurements.stall_longest_time.value).toEqual( + expect.any(Number) + ); + expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + } + + done(); + }, 500); + }); + + it("Stall tracking only measures stalls inside the final time when trimEnd is used", (done) => { + const stallTracking = new StallTrackingInstrumentation(); + + const transaction = new Transaction({ + name: "Test Transaction", + trimEnd: true, + sampled: true, + }); + transaction.initSpanRecorder(); + + stallTracking.onTransactionStart(transaction); + + // Span will be finished + const span = transaction.startChild({ + description: "To Finish", + }); + + setTimeout(() => { + span.finish(); + }, 200); + + setTimeout(() => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + const measurements = getLastEvent()?.measurements; + + expect(measurements).toBeDefined(); + + if (measurements) { + expect(measurements.stall_count.value).toEqual(1); + expect(measurements.stall_longest_time.value).toEqual( + expect.any(Number) + ); + expect(measurements.stall_total_time.value).toEqual(expect.any(Number)); + } + + done(); + }, 500); + + setTimeout(() => { + // this should be run after the span finishes, and not logged. + expensiveOperation(); + }, 300); + + expensiveOperation(); + }); + + it("Stall tracking does not track the first transaction if more than 10 are running", () => { + const stallTracking = new StallTrackingInstrumentation(); + + const transactions = new Array(11).fill(0).map((_, i) => { + const transaction = new Transaction({ + name: `Test Transaction ${i}`, + sampled: true, + }); + + stallTracking.onTransactionStart(transaction); + + return transaction; + }); + + stallTracking.onTransactionFinish(transactions[0]); + transactions[0].finish(); + const measurements0 = getLastEvent()?.measurements; + expect(measurements0).toBeUndefined(); + + stallTracking.onTransactionFinish(transactions[1]); + transactions[1].finish(); + const measurements1 = getLastEvent()?.measurements; + expect(measurements1).toBeDefined(); + + transactions.slice(2).forEach((transaction) => { + stallTracking.onTransactionFinish(transaction); + transaction.finish(); + }); + }); }); From 0530b25a448195180a942654ad44813b4ab1f8fa Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Wed, 25 Aug 2021 19:18:18 -0500 Subject: [PATCH 05/14] fix: Remove error boundary and fix types --- src/js/options.ts | 5 +++-- src/js/sdk.tsx | 26 ++++++++++---------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/js/options.ts b/src/js/options.ts index c0a8a4fe02..85ae95cbaa 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -1,5 +1,4 @@ import { BrowserOptions } from "@sentry/react"; -import { ErrorBoundaryProps } from "@sentry/react/dist/errorboundary"; import { ProfilerProps } from "@sentry/react/dist/profiler"; import { TouchEventBoundaryProps } from "./touchevents"; @@ -75,7 +74,9 @@ export interface ReactNativeOptions extends BrowserOptions { } export interface ReactNativeWrapperOptions extends ReactNativeOptions { - errorBoundaryProps?: ErrorBoundaryProps; + /** Props for the root React profiler */ profilerProps?: ProfilerProps; + + /** Props for the root touch event boundary */ touchEventBoundaryProps?: TouchEventBoundaryProps; } diff --git a/src/js/sdk.tsx b/src/js/sdk.tsx index 0c4bff1a21..579deb71c3 100644 --- a/src/js/sdk.tsx +++ b/src/js/sdk.tsx @@ -1,11 +1,7 @@ import { initAndBind, setExtra } from "@sentry/core"; import { Hub, makeMain } from "@sentry/hub"; import { RewriteFrames } from "@sentry/integrations"; -import { - defaultIntegrations, - ErrorBoundary, - getCurrentHub, -} from "@sentry/react"; +import { defaultIntegrations, getCurrentHub } from "@sentry/react"; import { StackFrame } from "@sentry/types"; import { getGlobalObject, logger } from "@sentry/utils"; import * as React from "react"; @@ -120,10 +116,10 @@ export function init(options: ReactNativeOptions): void { /** * Inits the Sentry React Native SDK with automatic instrumentation and wrapped features. */ -export function initWith( - RootComponent: React.ComponentType, +export function initWith

( + RootComponent: React.ComponentType

, passedOptions: ReactNativeWrapperOptions -): React.FC { +): React.ComponentType

{ const options = _init(passedOptions); const tracingIntegration = getCurrentHub().getIntegration(ReactNativeTracing); @@ -136,15 +132,13 @@ export function initWith( name: RootComponent.displayName ?? "Root", }; - const RootApp: React.FC = (appProps) => { + const RootApp: React.FC

= (appProps) => { return ( - - - - - - - + + + + + ); }; From 513385d90c8092cdedb9e0d86955bb3245aa9cc6 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 16:46:27 -0500 Subject: [PATCH 06/14] build(js): Bump sentry-javascript dependencies to 6.12.0-beta.2 --- package.json | 20 +++---- yarn.lock | 144 +++++++++++++++++++++++++-------------------------- 2 files changed, 82 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index cdcf1a00fe..4d2d16a163 100644 --- a/package.json +++ b/package.json @@ -40,19 +40,19 @@ "react-native": ">=0.56.0" }, "dependencies": { - "@sentry/browser": "6.7.1", - "@sentry/core": "6.7.1", - "@sentry/hub": "6.7.1", - "@sentry/integrations": "6.7.1", - "@sentry/react": "6.7.1", - "@sentry/tracing": "6.7.1", - "@sentry/types": "6.7.1", - "@sentry/utils": "6.7.1", + "@sentry/browser": "6.12.0-beta.2", + "@sentry/core": "6.12.0-beta.2", + "@sentry/hub": "6.12.0-beta.2", + "@sentry/integrations": "6.12.0-beta.2", + "@sentry/react": "6.12.0-beta.2", + "@sentry/tracing": "6.12.0-beta.2", + "@sentry/types": "6.12.0-beta.2", + "@sentry/utils": "6.12.0-beta.2", "@sentry/wizard": "^1.2.2" }, "devDependencies": { - "@sentry-internal/eslint-config-sdk": "6.7.1", - "@sentry-internal/eslint-plugin-sdk": "6.7.1", + "@sentry-internal/eslint-config-sdk": "6.12.0-beta.2", + "@sentry-internal/eslint-plugin-sdk": "6.12.0-beta.2", "@sentry/typescript": "^5.20.0", "@types/jest": "^26.0.15", "@types/react": "^16.9.49", diff --git a/yarn.lock b/yarn.lock index b122666c29..f26b66e9ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1123,13 +1123,13 @@ sudo-prompt "^9.0.0" wcwidth "^1.0.1" -"@sentry-internal/eslint-config-sdk@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-config-sdk/-/eslint-config-sdk-6.7.1.tgz#e258b236511e8889fe95cc49ca2c15abbc803250" - integrity sha512-K7t/WLm5q2klB8wqt+7MgtNDyZEu7Q5HO/ur77zStcNmzi60A8ZnTT8cBZrKHtxc8vOuwLzcoCgM+KIWJOZSpg== +"@sentry-internal/eslint-config-sdk@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-config-sdk/-/eslint-config-sdk-6.12.0-beta.2.tgz#44b417fb26f209992f7d08577c7485ce3535f2a1" + integrity sha512-KiBL/nrfVhTw9rPAlNGC6Rey7s93mhpHdqogB/BtMNx3oRAYzKFzOWFwfqaWY6QFUKvlP0WXaSyc72akm648Lw== dependencies: - "@sentry-internal/eslint-plugin-sdk" "6.7.1" - "@sentry-internal/typescript" "6.7.1" + "@sentry-internal/eslint-plugin-sdk" "6.12.0-beta.2" + "@sentry-internal/typescript" "6.12.0-beta.2" "@typescript-eslint/eslint-plugin" "^3.9.0" "@typescript-eslint/parser" "^3.9.0" eslint-config-prettier "^6.11.0" @@ -1138,29 +1138,29 @@ eslint-plugin-jsdoc "^30.0.3" eslint-plugin-simple-import-sort "^5.0.3" -"@sentry-internal/eslint-plugin-sdk@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-plugin-sdk/-/eslint-plugin-sdk-6.7.1.tgz#6068ba3fde062bd4cc7bef3189ddd797b5c7bf18" - integrity sha512-rCZMHo4twjFhNFq8uOF41ZaUdsHWl2fmEsqJk6ufw3QRZEAgfGRSilwnTW1EFEfCi+Fj92Em7rq5HwXNYqURzQ== +"@sentry-internal/eslint-plugin-sdk@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-plugin-sdk/-/eslint-plugin-sdk-6.12.0-beta.2.tgz#8dc8854b644f7c295abcfe34b5ff6d66985a451b" + integrity sha512-H3YZNCQH8fmzdFan+Brh4bFtxnlQJpU8Qjv+V335LG+XG7rt3/nsUYYjzT71Ajr5F0n//bdyXQTMXoOabHQTSQ== dependencies: requireindex "~1.1.0" -"@sentry-internal/typescript@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/typescript/-/typescript-6.7.1.tgz#9de1a49c244ff44ea01865c91d96275187281a60" - integrity sha512-73Rscw2P5DBnVOWn1KbaYK2VjAoMD3POwf/3QrIPe5H9iBlXcU4ZdXZvvsajE+r+GMhCse9U3tIYvC4oEptKhQ== +"@sentry-internal/typescript@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/typescript/-/typescript-6.12.0-beta.2.tgz#c96de33e91b5ae4cb655afb11e287518dcaa6a04" + integrity sha512-a1dPWbS/RDXU70Qv0iievZoFUPmjUJfG+ZSDSFxokNcxzayl+bvpVhePxkDlZQQZgQEOT4FLDBs/E9wAiS/N3Q== dependencies: tslint-config-prettier "^1.18.0" tslint-consistent-codestyle "^1.15.1" -"@sentry/browser@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.1.tgz#e01144a08984a486ecc91d7922cc457e9c9bd6b7" - integrity sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA== +"@sentry/browser@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.12.0-beta.2.tgz#6cb5e49aa2eabeffee8e3b1440aa7dfc4cb57996" + integrity sha512-p+4hf0/HOCJ8Y8ge5LyyxsNBtktFLvFj2j9DblkHvmAQaMjSKrK87B2zuMAW+FQjofUsvfJDRpGI1qOD0d5aXQ== dependencies: - "@sentry/core" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/core" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" "@sentry/cli@^1.52.4": @@ -1174,72 +1174,72 @@ progress "^2.0.3" proxy-from-env "^1.1.0" -"@sentry/core@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.7.1.tgz#c3aaa6415d06bec65ac446b13b84f073805633e3" - integrity sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA== +"@sentry/core@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.12.0-beta.2.tgz#93aefbe8711a494e069dc122577bc4a79bfe29ca" + integrity sha512-gewpA+GuxcCSPcAXNTJjJKZhCNgN21lb3XonjXX6Vf7tIc7HQl+MQI4HcPN8gx5LY78VcwC1lyOtqU8+tHwb8Q== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/hub@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.1.tgz#d46d24deec67f0731a808ca16796e6765b371bc1" - integrity sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg== +"@sentry/hub@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.12.0-beta.2.tgz#87db87f124a9eb5ed1399b47511901086feb3a37" + integrity sha512-zZSFbWAFDsRCHsgFzIVlZUK6r96pIiwi9Ut1eIlremtuyhTCWqjaLB7uc78bO62X00mCHFTI/l8YurCpKnwsug== dependencies: - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/integrations@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.7.1.tgz#9a6723e35589dfdb13c2cd22259184946f0b275e" - integrity sha512-nWxAPTunZxE+E6bi4FyhKHXcUUVpbSpvtwvdHiw/K72p7FuX/K0qU002Ltdfs4U1nyMIjesE776IGMrBLU67uA== +"@sentry/integrations@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.12.0-beta.2.tgz#05931deea00f5ebeedf425f1906c46ce23ba62c2" + integrity sha512-tY4tZKkloLwaOVy4NHQKewu3sGfbJoaWIxMjyGTJVHouNjC2GOgEVQLe3DvfHAkGqVTYJhEOvMhwAsoAzlIzvQ== dependencies: - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" localforage "^1.8.1" tslib "^1.9.3" -"@sentry/minimal@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.1.tgz#babf85ee2f167e9dcf150d750d7a0b250c98449c" - integrity sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw== +"@sentry/minimal@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.12.0-beta.2.tgz#d9c8c0ba731ca7fdaa645bb11fa39681137608a0" + integrity sha512-nM7NnCFBIvjwR80y5xdQ80NjOJia7ApctRPcB+bZo3h096JH1me2FxnQzZzu38WUbnrbh7GhnisDoggoO2ihIQ== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/types" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/react@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.7.1.tgz#7d69b9509ee1c08fd20f41b2bd3452f061524c83" - integrity sha512-kLswcfwkq+Pv4ZAQ0Tq1X3PUx0t/glD3kRRSQ0ZGn4zdQWhkTkIaVeSrxfU+K9nwZisVEAVXtMJadk4X2KNySA== +"@sentry/react@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.12.0-beta.2.tgz#60e23f8655eb588f2989524cdcc50dbafe212f94" + integrity sha512-HgjRRb/xCEyww9DvN4dNPlNJxKR8q09HJLuCm6ik3T9bLHjVPaZzUBXdgde+itcORRH+o8EIFbGEwXOvMZjJCg== dependencies: - "@sentry/browser" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/browser" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.7.1.tgz#b11f0c17a6c5dc14ef44733e5436afb686683268" - integrity sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ== +"@sentry/tracing@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.12.0-beta.2.tgz#cb2bf46142e5a544aad63b89b5c167866343f304" + integrity sha512-v3P8WTOQsz+XDIQuH3TuKUKEIrc5vIZGOBRn90SZolMH9CwqTRyItRgnA54EmhMi5QhjQyW5Qw5wAUfIRGoSwA== dependencies: - "@sentry/hub" "6.7.1" - "@sentry/minimal" "6.7.1" - "@sentry/types" "6.7.1" - "@sentry/utils" "6.7.1" + "@sentry/hub" "6.12.0-beta.2" + "@sentry/minimal" "6.12.0-beta.2" + "@sentry/types" "6.12.0-beta.2" + "@sentry/utils" "6.12.0-beta.2" tslib "^1.9.3" -"@sentry/types@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b" - integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA== +"@sentry/types@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0-beta.2.tgz#47cc7e51163128fa3423bc97314a50c9e8146645" + integrity sha512-Oz/aUeSGW7dNMcCadZU60tv04BI3YZpBWgYP0uvF5F7fu6dPclxaSeH4JlU4RIfJ1jcM44hefnqUs16mJW4ZHg== "@sentry/typescript@^5.20.0": version "5.20.1" @@ -1249,12 +1249,12 @@ tslint-config-prettier "^1.18.0" tslint-consistent-codestyle "^1.15.1" -"@sentry/utils@6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.1.tgz#909184ad580f0f6375e1e4d4a6ffd33dfe64a4d1" - integrity sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA== +"@sentry/utils@6.12.0-beta.2": + version "6.12.0-beta.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.12.0-beta.2.tgz#88ee87f57f2723c4364a94010e4be8a82c634f10" + integrity sha512-rTxBcEoqvHNVoIHubXF6vDBmUghq+6UGUyv3y8FKTeVazeCJgsw/ssLWwcaop6GoyBOrH17ljetmHI3ZgpMSdw== dependencies: - "@sentry/types" "6.7.1" + "@sentry/types" "6.12.0-beta.2" tslib "^1.9.3" "@sentry/wizard@^1.2.2": From 3c37b73cf95430a11a4bd20dd8ce0f746071564f Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 16:46:35 -0500 Subject: [PATCH 07/14] fix: Fix transport to have taskProducer --- src/js/transports/native.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/transports/native.ts b/src/js/transports/native.ts index 28bc571d83..aaa0f3d771 100644 --- a/src/js/transports/native.ts +++ b/src/js/transports/native.ts @@ -1,8 +1,6 @@ -// import { Transports } from "@sentry/react"; import { Event, Response, Transport } from "@sentry/types"; import { PromiseBuffer, SentryError } from "@sentry/utils"; -// import { Platform } from "react-native"; import { NATIVE } from "../wrapper"; /** Native Transport class implementation */ @@ -19,7 +17,7 @@ export class NativeTransport implements Transport { new SentryError("Not adding Promise due to buffer limit reached.") ); } - return this._buffer.add(NATIVE.sendEvent(event)); + return this._buffer.add(() => NATIVE.sendEvent(event)); } /** From dfb63d49791554dba53015d1c2cefa41a930d41d Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 16:46:41 -0500 Subject: [PATCH 08/14] fix: Fix sdk test --- test/sdk.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/sdk.test.ts b/test/sdk.test.ts index 6b56ea383f..94e4381847 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -1,11 +1,14 @@ import { logger } from "@sentry/utils"; jest.mock("@sentry/react", () => { + const actualModule = jest.requireActual("@sentry/react"); + const mockClient = { flush: jest.fn(() => Promise.resolve(true)), }; return { + ...actualModule, getCurrentHub: jest.fn(() => ({ getClient: jest.fn(() => mockClient), setTag: jest.fn(), From 533bc7dc87dc9cdabe8e9de2a315fb92f252a6e6 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 16:48:47 -0500 Subject: [PATCH 09/14] unignore yalc --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index aad533aa28..c843fcfb21 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,3 @@ wheelhouse # Android .idea/ - -# Yalc -.yalc -yalc.lock From ccd5d63d047864ef30b5f0a4b4fc4183a251121a Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 16:49:59 -0500 Subject: [PATCH 10/14] ref: sample comment --- sample/src/App.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sample/src/App.tsx b/sample/src/App.tsx index 84e1fb4420..b2871ea810 100644 --- a/sample/src/App.tsx +++ b/sample/src/App.tsx @@ -64,8 +64,6 @@ const options = { dist: `${packageVersion}.0`, }; -// Sentry.init(options); - const Stack = createStackNavigator(); const App = () => { @@ -96,4 +94,5 @@ const App = () => { ); }; +// We use initWith to wrap your app with more features out of the box such as auto performance monitoring. export default Sentry.initWith(App, options); From c163d9e7cc586b4faf0e75d5633ac73cebc170ce Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 17:04:40 -0500 Subject: [PATCH 11/14] feat: Stall tracking flag --- src/js/tracing/reactnativetracing.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/js/tracing/reactnativetracing.ts b/src/js/tracing/reactnativetracing.ts index ed2aa8a015..4ae0c2dbe3 100644 --- a/src/js/tracing/reactnativetracing.ts +++ b/src/js/tracing/reactnativetracing.ts @@ -87,6 +87,11 @@ export interface ReactNativeTracingOptions * Track slow/frozen frames from the native layer and adds them as measurements to all transactions. */ enableNativeFramesTracking: boolean; + + /** + * Track when and how long the JS event loop stalls for. Adds stalls as measurements to all transactions. + */ + enableStallTracking: boolean; } const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { @@ -97,6 +102,7 @@ const defaultReactNativeTracingOptions: ReactNativeTracingOptions = { beforeNavigate: (context) => context, enableAppStartTracking: true, enableNativeFramesTracking: true, + enableStallTracking: true, }; /** @@ -148,6 +154,7 @@ export class ReactNativeTracing implements Integration { routingInstrumentation, enableAppStartTracking, enableNativeFramesTracking, + enableStallTracking, } = this.options; this._getCurrentHub = getCurrentHub; @@ -169,11 +176,14 @@ export class ReactNativeTracing implements Integration { return false; } ); - this.stallTrackingInstrumentation = new StallTrackingInstrumentation(); } else { NATIVE.disableNativeFramesTracking(); } + if (enableStallTracking) { + this.stallTrackingInstrumentation = new StallTrackingInstrumentation(); + } + if (routingInstrumentation) { routingInstrumentation.registerRoutingInstrumentation( this._onRouteWillChange.bind(this), From 1dcae6cadd97e850d8aadb52e4963efc2ab2315e Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Thu, 26 Aug 2021 17:07:31 -0500 Subject: [PATCH 12/14] meta: Changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4015a30a9..f9d27ab4c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- feat: Add `initWith` wrapper-initialization method #1728 +- feat: App-start measurements, if using the `initWith` wrapper, will now finish on the root component mount #1728 + ## 3.0.0-beta.2 - feat: Native slow/frozen frames measurements #1711 From a16b946c65f4f3899b0059f88ea0716f24592fd1 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Fri, 27 Aug 2021 15:32:28 -0500 Subject: [PATCH 13/14] fix: Separate wrapper method --- sample/src/App.tsx | 8 ++++---- src/js/index.ts | 2 +- src/js/options.ts | 2 +- src/js/sdk.tsx | 21 +++++---------------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/sample/src/App.tsx b/sample/src/App.tsx index b2871ea810..d8628d2681 100644 --- a/sample/src/App.tsx +++ b/sample/src/App.tsx @@ -25,7 +25,7 @@ const reactNavigationV5Instrumentation = new Sentry.ReactNavigationV5Instrumenta routeChangeTimeoutMs: 500, // How long it will wait for the route change to complete. Default is 1000ms }, ); -const options = { +Sentry.init({ // Replace the example DSN below with your own DSN: dsn: SENTRY_INTERNAL_DSN, debug: true, @@ -62,7 +62,7 @@ const options = { // otherwise they will not work. release: packageVersion, dist: `${packageVersion}.0`, -}; +}); const Stack = createStackNavigator(); @@ -94,5 +94,5 @@ const App = () => { ); }; -// We use initWith to wrap your app with more features out of the box such as auto performance monitoring. -export default Sentry.initWith(App, options); +// Wrap your app to get more features out of the box such as auto performance monitoring. +export default Sentry.wrap(App); diff --git a/src/js/index.ts b/src/js/index.ts index 2ee4658ce4..efedd78ea1 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -62,7 +62,7 @@ export { ReactNativeClient } from "./client"; export { init, - initWith, + wrap, // eslint-disable-next-line deprecation/deprecation setDist, // eslint-disable-next-line deprecation/deprecation diff --git a/src/js/options.ts b/src/js/options.ts index 85ae95cbaa..914ab09424 100644 --- a/src/js/options.ts +++ b/src/js/options.ts @@ -73,7 +73,7 @@ export interface ReactNativeOptions extends BrowserOptions { enableAutoPerformanceTracking?: boolean; } -export interface ReactNativeWrapperOptions extends ReactNativeOptions { +export interface ReactNativeWrapperOptions { /** Props for the root React profiler */ profilerProps?: ProfilerProps; diff --git a/src/js/sdk.tsx b/src/js/sdk.tsx index 579deb71c3..9f6f7eff6c 100644 --- a/src/js/sdk.tsx +++ b/src/js/sdk.tsx @@ -33,7 +33,7 @@ const DEFAULT_OPTIONS: ReactNativeOptions = { /** * Inits the SDK and returns the final options. */ -function _init(passedOptions: O): O { +export function init(passedOptions: ReactNativeOptions): void { const reactNativeHub = new Hub(undefined, new ReactNativeScope()); makeMain(reactNativeHub); @@ -102,39 +102,28 @@ function _init(passedOptions: O): O { if (getGlobalObject().HermesInternal) { getCurrentHub().setTag("hermes", "true"); } - - return options; -} - -/** - * Inits the Sentry React Native SDK without any wrapping - */ -export function init(options: ReactNativeOptions): void { - _init(options); } /** * Inits the Sentry React Native SDK with automatic instrumentation and wrapped features. */ -export function initWith

( +export function wrap

( RootComponent: React.ComponentType

, - passedOptions: ReactNativeWrapperOptions + options?: ReactNativeWrapperOptions ): React.ComponentType

{ - const options = _init(passedOptions); - const tracingIntegration = getCurrentHub().getIntegration(ReactNativeTracing); if (tracingIntegration) { tracingIntegration.useAppStartWithProfiler = true; } const profilerProps = { - ...options.profilerProps, + ...(options?.profilerProps ?? {}), name: RootComponent.displayName ?? "Root", }; const RootApp: React.FC

= (appProps) => { return ( - + From 270f30c7c8282f4c78ceed26d8e65e373c12ecc6 Mon Sep 17 00:00:00 2001 From: Jenn Mueng Date: Fri, 27 Aug 2021 15:42:22 -0500 Subject: [PATCH 14/14] meta: Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d27ab4c0..37b7ffd5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -- feat: Add `initWith` wrapper-initialization method #1728 -- feat: App-start measurements, if using the `initWith` wrapper, will now finish on the root component mount #1728 +- feat: Add `wrap` wrapper method with profiler and touch event boundary #1728 +- feat: App-start measurements, if using the `wrap` wrapper, will now finish on the root component mount #1728 ## 3.0.0-beta.2