Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@

### Features

- Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide to proceed with just renaming the androidProfilingOptions to profilingOptions in the conversation above let's add a note to the changelog

Suggested change
- Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611))
- Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611))
- Note that Android is also now using `profilingOptions` instead of `androidProfilingOptions`

```js
Sentry.init({
_experiments: {
profilingOptions: {
profileSessionSampleRate: 1.0,
lifecycle: 'trace', // or 'manual'
startOnAppStart: true,
},
},
});
```
- 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,47 +494,44 @@ 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) {
@Nullable 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",
profileSessionSampleRate));
"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));
SentryLevel.INFO, String.format("Profiling startOnAppStart set to %b", startOnAppStart));
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/core/ios/RNSentryExperimentalOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 42 additions & 0 deletions packages/core/ios/RNSentryExperimentalOptions.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "RNSentryExperimentalOptions.h"
#import <Sentry/SentryProfilingConditionals.h>
@import Sentry;

@implementation RNSentryExperimentalOptions
Expand Down Expand Up @@ -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
7 changes: 7 additions & 0 deletions packages/core/ios/SentrySDKWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ + (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) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ export class ReactNativeClient extends Client<ReactNativeClientOptions> {
'options' in this._integrations[MOBILE_REPLAY_INTEGRATION_NAME]
? (this._integrations[MOBILE_REPLAY_INTEGRATION_NAME] as ReturnType<typeof mobileReplayIntegration>).options
: undefined,
androidProfilingOptions: this._options._experiments?.androidProfilingOptions,
profilingOptions:
this._options._experiments?.profilingOptions ?? this._options._experiments?.androidProfilingOptions,
})
.then(
(result: boolean) => {
Expand Down
29 changes: 19 additions & 10 deletions packages/core/src/js/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,26 @@ export interface BaseReactNativeOptions {
*/
enableUnhandledCPPExceptionsV2?: boolean;

/**
* 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
*/
profilingOptions?: ProfilingOptions;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep androidProfilingOptions and mark it as deprecated?
We can merge both values on the wrapper init and on a later version remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep androidProfilingOptions and mark it as deprecated?

Sounds good ๐Ÿ‘

Since it's marked as experimental everywhere (docs, changelog) I'm also ok to just proceed with the change.


/**
* Configuration options for Android UI profiling.
* UI profiling supports two modes: `manual` and `trace`.
* 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
* @deprecated Use `profilingOptions` instead. This option will be removed in the next major version.
*/
androidProfilingOptions?: AndroidProfilingOptions;
androidProfilingOptions?: ProfilingOptions;
};

/**
Expand Down Expand Up @@ -342,19 +352,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.
Expand All @@ -369,9 +378,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.
Expand Down
15 changes: 10 additions & 5 deletions packages/core/src/js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -57,7 +57,9 @@ export type NativeSdkOptions = Partial<ReactNativeClientOptions> & {
ignoreErrorsRegex?: string[] | undefined;
} & {
mobileReplayOptions: MobileReplayOptions | undefined;
androidProfilingOptions?: AndroidProfilingOptions | undefined;
profilingOptions?: ProfilingOptions | undefined;
/** @deprecated Use `profilingOptions` instead. */
androidProfilingOptions?: ProfilingOptions | undefined;
};

interface SentryNativeWrapper {
Expand Down Expand Up @@ -289,16 +291,19 @@ export const NATIVE: SentryNativeWrapper = {
integrations,
ignoreErrors,
logsOrigin,
profilingOptions,
androidProfilingOptions,
...filteredOptions
} = options;
/* eslint-enable @typescript-eslint/unbound-method,@typescript-eslint/no-unused-vars */

// Move androidProfilingOptions into _experiments
if (androidProfilingOptions) {
// Move profilingOptions into _experiments
// Support deprecated androidProfilingOptions for backwards compatibility
const resolvedProfilingOptions = profilingOptions ?? androidProfilingOptions;
if (resolvedProfilingOptions) {
filteredOptions._experiments = {
...filteredOptions._experiments,
androidProfilingOptions,
profilingOptions: resolvedProfilingOptions,
};
}

Expand Down
Loading
Loading