From 5c23ee94b51f5696d944decd5b3bd1a3613422d7 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 3 Feb 2026 11:14:15 +0100 Subject: [PATCH 1/6] iOS UI Profiling --- .../io/sentry/react/RNSentryModuleImpl.java | 26 ++++++------ .../core/ios/RNSentryExperimentalOptions.h | 8 ++++ .../core/ios/RNSentryExperimentalOptions.m | 42 +++++++++++++++++++ packages/core/ios/SentrySDKWrapper.m | 8 ++++ packages/core/src/js/client.ts | 2 +- packages/core/src/js/options.ts | 19 ++++----- packages/core/src/js/wrapper.ts | 12 +++--- ...oidProfiling.test.ts => profiling.test.ts} | 26 ++++++------ 8 files changed, 100 insertions(+), 43 deletions(-) rename packages/core/test/{androidProfiling.test.ts => profiling.test.ts} (85%) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 11a4b40c53..c97a777e00 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -494,47 +494,47 @@ private void configureAndroidProfiling( } @Nullable final ReadableMap experiments = rnOptions.getMap("_experiments"); - if (experiments == null || !experiments.hasKey("androidProfilingOptions")) { + if (experiments == null || !experiments.hasKey("profilingOptions")) { return; } @Nullable - final ReadableMap androidProfilingOptions = experiments.getMap("androidProfilingOptions"); - if (androidProfilingOptions == null) { + final ReadableMap profilingOptions = experiments.getMap("profilingOptions"); + if (profilingOptions == null) { return; } // Set profile session sample rate - if (androidProfilingOptions.hasKey("profileSessionSampleRate")) { + if (profilingOptions.hasKey("profileSessionSampleRate")) { final double profileSessionSampleRate = - androidProfilingOptions.getDouble("profileSessionSampleRate"); + profilingOptions.getDouble("profileSessionSampleRate"); options.setProfileSessionSampleRate(profileSessionSampleRate); logger.log( SentryLevel.INFO, String.format( - "Android UI Profiling profileSessionSampleRate set to: %.2f", + "UI Profiling profileSessionSampleRate set to: %.2f", profileSessionSampleRate)); } // Set profiling lifecycle mode - if (androidProfilingOptions.hasKey("lifecycle")) { - final String lifecycle = androidProfilingOptions.getString("lifecycle"); + if (profilingOptions.hasKey("lifecycle")) { + final String lifecycle = profilingOptions.getString("lifecycle"); if ("manual".equalsIgnoreCase(lifecycle)) { options.setProfileLifecycle(ProfileLifecycle.MANUAL); - logger.log(SentryLevel.INFO, "Android UI Profile Lifecycle set to MANUAL"); + logger.log(SentryLevel.INFO, "UI Profile Lifecycle set to MANUAL"); } else if ("trace".equalsIgnoreCase(lifecycle)) { options.setProfileLifecycle(ProfileLifecycle.TRACE); - logger.log(SentryLevel.INFO, "Android UI Profile Lifecycle set to TRACE"); + logger.log(SentryLevel.INFO, "UI Profile Lifecycle set to TRACE"); } } // Set start on app start - if (androidProfilingOptions.hasKey("startOnAppStart")) { - final boolean startOnAppStart = androidProfilingOptions.getBoolean("startOnAppStart"); + if (profilingOptions.hasKey("startOnAppStart")) { + final boolean startOnAppStart = profilingOptions.getBoolean("startOnAppStart"); options.setStartProfilerOnAppStart(startOnAppStart); logger.log( SentryLevel.INFO, - String.format("Android UI Profiling startOnAppStart set to %b", startOnAppStart)); + String.format("Profiling startOnAppStart set to %b", startOnAppStart)); } } diff --git a/packages/core/ios/RNSentryExperimentalOptions.h b/packages/core/ios/RNSentryExperimentalOptions.h index ec0501cb05..eece5af374 100644 --- a/packages/core/ios/RNSentryExperimentalOptions.h +++ b/packages/core/ios/RNSentryExperimentalOptions.h @@ -37,6 +37,14 @@ NS_ASSUME_NONNULL_BEGIN + (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled sentryOptions:(SentryOptions *)sentryOptions; +/** + * Configures iOS UI profiling options on SentryOptions + * @param profilingOptions Dictionary containing profiling configuration + * @param sentryOptions The SentryOptions instance to configure + */ ++ (void)configureProfilingWithOptions:(NSDictionary *)profilingOptions + sentryOptions:(SentryOptions *)sentryOptions; + @end NS_ASSUME_NONNULL_END diff --git a/packages/core/ios/RNSentryExperimentalOptions.m b/packages/core/ios/RNSentryExperimentalOptions.m index 7e0974e527..084ed36309 100644 --- a/packages/core/ios/RNSentryExperimentalOptions.m +++ b/packages/core/ios/RNSentryExperimentalOptions.m @@ -1,4 +1,5 @@ #import "RNSentryExperimentalOptions.h" +#import @import Sentry; @implementation RNSentryExperimentalOptions @@ -36,4 +37,45 @@ + (void)setEnableSessionReplayInUnreliableEnvironment:(BOOL)enabled sentryOptions.experimental.enableSessionReplayInUnreliableEnvironment = enabled; } ++ (void)configureProfilingWithOptions:(NSDictionary *)profilingOptions + sentryOptions:(SentryOptions *)sentryOptions +{ +#if SENTRY_TARGET_PROFILING_SUPPORTED + if (sentryOptions == nil || profilingOptions == nil) { + return; + } + + sentryOptions.configureProfiling = ^(SentryProfileOptions *_Nonnull profiling) { + // Set session sample rate + id profileSessionSampleRate = profilingOptions[@"profileSessionSampleRate"]; + if (profileSessionSampleRate != nil && + [profileSessionSampleRate isKindOfClass:[NSNumber class]]) { + profiling.sessionSampleRate = [profileSessionSampleRate floatValue]; + NSLog(@"Sentry: UI Profiling sessionSampleRate set to: %.2f", + profiling.sessionSampleRate); + } + + // Set lifecycle mode + NSString *lifecycle = profilingOptions[@"lifecycle"]; + if ([lifecycle isKindOfClass:[NSString class]]) { + if ([lifecycle caseInsensitiveCompare:@"manual"] == NSOrderedSame) { + profiling.lifecycle = SentryProfileLifecycleManual; + NSLog(@"Sentry: UI Profiling Lifecycle set to MANUAL"); + } else if ([lifecycle caseInsensitiveCompare:@"trace"] == NSOrderedSame) { + profiling.lifecycle = SentryProfileLifecycleTrace; + NSLog(@"Sentry: UI Profiling Lifecycle set to TRACE"); + } + } + + // Set profile app starts + id startOnAppStart = profilingOptions[@"startOnAppStart"]; + if (startOnAppStart != nil && [startOnAppStart isKindOfClass:[NSNumber class]]) { + profiling.profileAppStarts = [startOnAppStart boolValue]; + NSLog(@"Sentry: UI Profiling profileAppStarts set to %@", + profiling.profileAppStarts ? @"YES" : @"NO"); + } + }; +#endif +} + @end diff --git a/packages/core/ios/SentrySDKWrapper.m b/packages/core/ios/SentrySDKWrapper.m index c11782d5ad..6a04753459 100644 --- a/packages/core/ios/SentrySDKWrapper.m +++ b/packages/core/ios/SentrySDKWrapper.m @@ -106,6 +106,14 @@ + (SentryOptions *)createOptionsWithDictionary:(NSDictionary *)options [experiments[@"enableUnhandledCPPExceptionsV2"] boolValue]; [RNSentryExperimentalOptions setEnableUnhandledCPPExceptionsV2:enableUnhandledCPPExceptions sentryOptions:sentryOptions]; + + // Configure iOS UI Profiling + NSDictionary *profilingOptions = experiments[@"profilingOptions"]; + if (profilingOptions != nil && + [profilingOptions isKindOfClass:[NSDictionary class]]) { + [RNSentryExperimentalOptions configureProfilingWithOptions:profilingOptions + sentryOptions:sentryOptions]; + } } if (isSessionReplayEnabled) { diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts index 699c94cd0e..b411ee7945 100644 --- a/packages/core/src/js/client.ts +++ b/packages/core/src/js/client.ts @@ -224,7 +224,7 @@ export class ReactNativeClient extends Client { 'options' in this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] ? (this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] as ReturnType).options : undefined, - androidProfilingOptions: this._options._experiments?.androidProfilingOptions, + profilingOptions: this._options._experiments?.profilingOptions, }) .then( (result: boolean) => { diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 859e876839..2ba2f25b71 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -295,15 +295,15 @@ export interface BaseReactNativeOptions { enableUnhandledCPPExceptionsV2?: boolean; /** - * Configuration options for Android UI profiling. - * UI profiling supports two modes: `manual` and `trace`. + * Configuration options for UI profiling. + * It supports two modes: `manual` and `trace`. * - In `trace` mode, the profiler runs based on active sampled spans. * - In `manual` mode, profiling is controlled via start/stop API calls. * * @experimental * @platform android */ - androidProfilingOptions?: AndroidProfilingOptions; + profilingOptions?: ProfilingOptions; }; /** @@ -342,19 +342,18 @@ export interface BaseReactNativeOptions { export type SentryReplayQuality = 'low' | 'medium' | 'high'; /** - * Android UI profiling lifecycle modes. + * UI profiling lifecycle modes. * - `trace`: Profiler runs based on active sampled spans * - `manual`: Profiler is controlled manually via start/stop API calls */ -export type AndroidProfilingLifecycle = 'trace' | 'manual'; +export type ProfilingLifecycle = 'trace' | 'manual'; /** - * Configuration options for Android UI profiling. + * Configuration options for UI profiling. * * @experimental - * @platform android */ -export interface AndroidProfilingOptions { +export interface ProfilingOptions { /** * Sample rate for profiling sessions. * This is evaluated once per session and determines if profiling should be enabled for that session. @@ -369,9 +368,9 @@ export interface AndroidProfilingOptions { * - `trace`: Profiler runs while there is at least one active sampled span * - `manual`: Profiler is controlled manually via Sentry.profiler.startProfiler/stopProfiler * - * @default 'trace' + * @default 'manual' */ - lifecycle?: AndroidProfilingLifecycle; + lifecycle?: ProfilingLifecycle; /** * Enable profiling on app start. diff --git a/packages/core/src/js/wrapper.ts b/packages/core/src/js/wrapper.ts index 55fcb3a2e8..6f8d28602e 100644 --- a/packages/core/src/js/wrapper.ts +++ b/packages/core/src/js/wrapper.ts @@ -22,7 +22,7 @@ import type { NativeStackFrames, Spec, } from './NativeRNSentry'; -import type { AndroidProfilingOptions, ReactNativeClientOptions } from './options'; +import type { ProfilingOptions, ReactNativeClientOptions } from './options'; import type * as Hermes from './profiling/hermes'; import type { NativeAndroidProfileEvent, NativeProfileEvent } from './profiling/nativeTypes'; import type { MobileReplayOptions } from './replay/mobilereplay'; @@ -57,7 +57,7 @@ export type NativeSdkOptions = Partial & { ignoreErrorsRegex?: string[] | undefined; } & { mobileReplayOptions: MobileReplayOptions | undefined; - androidProfilingOptions?: AndroidProfilingOptions | undefined; + profilingOptions?: ProfilingOptions | undefined; }; interface SentryNativeWrapper { @@ -289,16 +289,16 @@ export const NATIVE: SentryNativeWrapper = { integrations, ignoreErrors, logsOrigin, - androidProfilingOptions, + profilingOptions, ...filteredOptions } = options; /* eslint-enable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */ - // Move androidProfilingOptions into _experiments - if (androidProfilingOptions) { + // Move profilingOptions into _experiments + if (profilingOptions) { filteredOptions._experiments = { ...filteredOptions._experiments, - androidProfilingOptions, + profilingOptions, }; } diff --git a/packages/core/test/androidProfiling.test.ts b/packages/core/test/profiling.test.ts similarity index 85% rename from packages/core/test/androidProfiling.test.ts rename to packages/core/test/profiling.test.ts index 68de0f1ca9..d6268dc038 100644 --- a/packages/core/test/androidProfiling.test.ts +++ b/packages/core/test/profiling.test.ts @@ -54,14 +54,14 @@ jest.mock('react-native', () => { const RNSentry = require('react-native').NativeModules.RNSentry as Spec; -describe('Android UI Profiling Options', () => { +describe('UI Profiling Options', () => { beforeEach(() => { NATIVE.platform = 'android'; NATIVE.enableNative = true; jest.clearAllMocks(); }); - it('passes androidProfilingOptions to native SDK', async () => { + it('passes profilingOptions to native SDK', async () => { await NATIVE.initNativeSdk({ dsn: 'https://example@sentry.io/123', enableNative: true, @@ -69,7 +69,7 @@ describe('Android UI Profiling Options', () => { devServerUrl: undefined, defaultSidecarUrl: undefined, mobileReplayOptions: undefined, - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 0.5, lifecycle: 'trace', startOnAppStart: true, @@ -79,7 +79,7 @@ describe('Android UI Profiling Options', () => { expect(RNSentry.initNativeSdk).toHaveBeenCalledWith( expect.objectContaining({ _experiments: expect.objectContaining({ - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 0.5, lifecycle: 'trace', startOnAppStart: true, @@ -89,7 +89,7 @@ describe('Android UI Profiling Options', () => { ); }); - it('passes androidProfilingOptions with manual lifecycle', async () => { + it('passes profilingOptions with manual lifecycle', async () => { await NATIVE.initNativeSdk({ dsn: 'https://example@sentry.io/123', enableNative: true, @@ -97,7 +97,7 @@ describe('Android UI Profiling Options', () => { devServerUrl: undefined, defaultSidecarUrl: undefined, mobileReplayOptions: undefined, - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 1.0, lifecycle: 'manual', startOnAppStart: false, @@ -107,7 +107,7 @@ describe('Android UI Profiling Options', () => { expect(RNSentry.initNativeSdk).toHaveBeenCalledWith( expect.objectContaining({ _experiments: expect.objectContaining({ - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 1.0, lifecycle: 'manual', startOnAppStart: false, @@ -117,7 +117,7 @@ describe('Android UI Profiling Options', () => { ); }); - it('does not pass androidProfilingOptions when undefined', async () => { + it('does not pass profilingOptions when undefined', async () => { await NATIVE.initNativeSdk({ dsn: 'https://example@sentry.io/123', enableNative: true, @@ -125,14 +125,14 @@ describe('Android UI Profiling Options', () => { devServerUrl: undefined, defaultSidecarUrl: undefined, mobileReplayOptions: undefined, - androidProfilingOptions: undefined, + profilingOptions: undefined, }); const callArgs = (RNSentry.initNativeSdk as jest.Mock).mock.calls[0][0]; - expect(callArgs._experiments?.androidProfilingOptions).toBeUndefined(); + expect(callArgs._experiments?.profilingOptions).toBeUndefined(); }); - it('handles partial androidProfilingOptions', async () => { + it('handles partial profilingOptions', async () => { await NATIVE.initNativeSdk({ dsn: 'https://example@sentry.io/123', enableNative: true, @@ -140,7 +140,7 @@ describe('Android UI Profiling Options', () => { devServerUrl: undefined, defaultSidecarUrl: undefined, mobileReplayOptions: undefined, - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 0.3, // lifecycle and startOnAppStart not provided }, @@ -149,7 +149,7 @@ describe('Android UI Profiling Options', () => { expect(RNSentry.initNativeSdk).toHaveBeenCalledWith( expect.objectContaining({ _experiments: expect.objectContaining({ - androidProfilingOptions: { + profilingOptions: { profileSessionSampleRate: 0.3, }, }), From e6786e515e9443c6b125572e7fe2a0cf23746023 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 3 Feb 2026 11:36:21 +0100 Subject: [PATCH 2/6] Changelog entry --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8604c6fc9..5621b15cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ ### Features +- Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611)) + ```js + Sentry.init({ + _experiments: { + profilingOptions: { + profileSessionSampleRate: 1.0, + lifecycle: 'trace', // or 'manual' + startOnAppStart: true, + }, + }, + }); + ``` - Add performance tracking for Expo Router route prefetching ([#5606](https://github.com/getsentry/sentry-react-native/pull/5606)) - New `wrapExpoRouter` utility to instrument manual `prefetch()` calls with performance spans - New `enablePrefetchTracking` option for `reactNavigationIntegration` to automatically track PRELOAD actions From 0cca6ac680fd9efd768cd8a55fc56b33741c6109 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 3 Feb 2026 11:50:54 +0100 Subject: [PATCH 3/6] Linter changes --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 5 ++--- packages/core/ios/SentrySDKWrapper.m | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index c97a777e00..4f7537da18 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -498,8 +498,7 @@ private void configureAndroidProfiling( return; } - @Nullable - final ReadableMap profilingOptions = experiments.getMap("profilingOptions"); + @Nullable final ReadableMap profilingOptions = experiments.getMap("profilingOptions"); if (profilingOptions == null) { return; } @@ -507,7 +506,7 @@ private void configureAndroidProfiling( // Set profile session sample rate if (profilingOptions.hasKey("profileSessionSampleRate")) { final double profileSessionSampleRate = - profilingOptions.getDouble("profileSessionSampleRate"); + profilingOptions.getDouble("profileSessionSampleRate"); options.setProfileSessionSampleRate(profileSessionSampleRate); logger.log( SentryLevel.INFO, diff --git a/packages/core/ios/SentrySDKWrapper.m b/packages/core/ios/SentrySDKWrapper.m index 6a04753459..7817e0855a 100644 --- a/packages/core/ios/SentrySDKWrapper.m +++ b/packages/core/ios/SentrySDKWrapper.m @@ -109,8 +109,7 @@ + (SentryOptions *)createOptionsWithDictionary:(NSDictionary *)options // Configure iOS UI Profiling NSDictionary *profilingOptions = experiments[@"profilingOptions"]; - if (profilingOptions != nil && - [profilingOptions isKindOfClass:[NSDictionary class]]) { + if (profilingOptions != nil && [profilingOptions isKindOfClass:[NSDictionary class]]) { [RNSentryExperimentalOptions configureProfilingWithOptions:profilingOptions sentryOptions:sentryOptions]; } From 015c486ce6e8b000491e2fd3f0e3e0c25e99ae35 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 3 Feb 2026 12:05:57 +0100 Subject: [PATCH 4/6] Lint fix --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 6 ++---- samples/expo/app.json | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 4f7537da18..a2229a354b 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -511,8 +511,7 @@ private void configureAndroidProfiling( logger.log( SentryLevel.INFO, String.format( - "UI Profiling profileSessionSampleRate set to: %.2f", - profileSessionSampleRate)); + "UI Profiling profileSessionSampleRate set to: %.2f", profileSessionSampleRate)); } // Set profiling lifecycle mode @@ -532,8 +531,7 @@ private void configureAndroidProfiling( final boolean startOnAppStart = profilingOptions.getBoolean("startOnAppStart"); options.setStartProfilerOnAppStart(startOnAppStart); logger.log( - SentryLevel.INFO, - String.format("Profiling startOnAppStart set to %b", startOnAppStart)); + SentryLevel.INFO, String.format("Profiling startOnAppStart set to %b", startOnAppStart)); } } diff --git a/samples/expo/app.json b/samples/expo/app.json index c3eb7ef323..f0ce75b461 100644 --- a/samples/expo/app.json +++ b/samples/expo/app.json @@ -14,9 +14,7 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "bundleIdentifier": "io.sentry.expo.sample", @@ -90,4 +88,4 @@ "url": "https://u.expo.dev/00000000-0000-0000-0000-000000000000" } } -} \ No newline at end of file +} From 2b891250fa58a93b1baa565e98544a7240548b02 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Wed, 4 Feb 2026 11:01:29 +0100 Subject: [PATCH 5/6] Deprecated `androidProfilingOptions` --- packages/core/src/js/client.ts | 3 +- packages/core/src/js/options.ts | 12 +++++- packages/core/src/js/wrapper.ts | 9 ++++- packages/core/test/profiling.test.ts | 60 ++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts index b411ee7945..74a834090a 100644 --- a/packages/core/src/js/client.ts +++ b/packages/core/src/js/client.ts @@ -224,7 +224,8 @@ export class ReactNativeClient extends Client { 'options' in this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] ? (this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] as ReturnType).options : undefined, - profilingOptions: this._options._experiments?.profilingOptions, + profilingOptions: + this._options._experiments?.profilingOptions ?? this._options._experiments?.androidProfilingOptions, }) .then( (result: boolean) => { diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 2ba2f25b71..20f2e7207d 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -301,9 +301,19 @@ export interface BaseReactNativeOptions { * - In `manual` mode, profiling is controlled via start/stop API calls. * * @experimental - * @platform android */ profilingOptions?: ProfilingOptions; + + /** + * Configuration options for Android UI profiling. + * It supports two modes: `manual` and `trace`. + * - In `trace` mode, the profiler runs based on active sampled spans. + * - In `manual` mode, profiling is controlled via start/stop API calls. + * + * @experimental + * @deprecated Use `profilingOptions` instead. This option will be removed in the next major version. + */ + androidProfilingOptions?: ProfilingOptions; }; /** diff --git a/packages/core/src/js/wrapper.ts b/packages/core/src/js/wrapper.ts index 6f8d28602e..35d686e39d 100644 --- a/packages/core/src/js/wrapper.ts +++ b/packages/core/src/js/wrapper.ts @@ -58,6 +58,8 @@ export type NativeSdkOptions = Partial & { } & { mobileReplayOptions: MobileReplayOptions | undefined; profilingOptions?: ProfilingOptions | undefined; + /** @deprecated Use `profilingOptions` instead. */ + androidProfilingOptions?: ProfilingOptions | undefined; }; interface SentryNativeWrapper { @@ -290,15 +292,18 @@ export const NATIVE: SentryNativeWrapper = { ignoreErrors, logsOrigin, profilingOptions, + androidProfilingOptions, ...filteredOptions } = options; /* eslint-enable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */ // Move profilingOptions into _experiments - if (profilingOptions) { + // Support deprecated androidProfilingOptions for backwards compatibility + const resolvedProfilingOptions = profilingOptions ?? androidProfilingOptions; + if (resolvedProfilingOptions) { filteredOptions._experiments = { ...filteredOptions._experiments, - profilingOptions, + profilingOptions: resolvedProfilingOptions, }; } diff --git a/packages/core/test/profiling.test.ts b/packages/core/test/profiling.test.ts index d6268dc038..8e16df0899 100644 --- a/packages/core/test/profiling.test.ts +++ b/packages/core/test/profiling.test.ts @@ -156,4 +156,64 @@ describe('UI Profiling Options', () => { }), ); }); + + describe('deprecated androidProfilingOptions', () => { + it('passes deprecated androidProfilingOptions as profilingOptions to native SDK', async () => { + await NATIVE.initNativeSdk({ + dsn: 'https://example@sentry.io/123', + enableNative: true, + autoInitializeNativeSdk: true, + devServerUrl: undefined, + defaultSidecarUrl: undefined, + mobileReplayOptions: undefined, + androidProfilingOptions: { + profileSessionSampleRate: 0.7, + lifecycle: 'trace', + startOnAppStart: true, + }, + }); + + expect(RNSentry.initNativeSdk).toHaveBeenCalledWith( + expect.objectContaining({ + _experiments: expect.objectContaining({ + profilingOptions: { + profileSessionSampleRate: 0.7, + lifecycle: 'trace', + startOnAppStart: true, + }, + }), + }), + ); + }); + + it('prefers profilingOptions over deprecated androidProfilingOptions', async () => { + await NATIVE.initNativeSdk({ + dsn: 'https://example@sentry.io/123', + enableNative: true, + autoInitializeNativeSdk: true, + devServerUrl: undefined, + defaultSidecarUrl: undefined, + mobileReplayOptions: undefined, + profilingOptions: { + profileSessionSampleRate: 0.5, + lifecycle: 'manual', + }, + androidProfilingOptions: { + profileSessionSampleRate: 0.9, + lifecycle: 'trace', + }, + }); + + expect(RNSentry.initNativeSdk).toHaveBeenCalledWith( + expect.objectContaining({ + _experiments: expect.objectContaining({ + profilingOptions: { + profileSessionSampleRate: 0.5, + lifecycle: 'manual', + }, + }), + }), + ); + }); + }); }); From 95f54e40d0599ccc7ab613520b0952a586f24d69 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Wed, 4 Feb 2026 11:04:07 +0100 Subject: [PATCH 6/6] Changelog entry update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5621b15cac..e11259edfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ }, }); ``` + - Note that the `androidProfilingOptions` key is now deprecated, and `profilingOptions` should be used instead - Add performance tracking for Expo Router route prefetching ([#5606](https://github.com/getsentry/sentry-react-native/pull/5606)) - New `wrapExpoRouter` utility to instrument manual `prefetch()` calls with performance spans - New `enablePrefetchTracking` option for `reactNavigationIntegration` to automatically track PRELOAD actions