diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d09d6c1a..7abd2a46ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Features +- Add `enableNdkAppHangTracking` and `ndkAppHangTimeoutIntervalMillis` options to enable Android NDK app hang tracking ([#6548](https://github.com/getsentry/sentry-react-native/pull/6548)) - Add `enableMetricKit` option to enable the iOS MetricKit integration ([#6540](https://github.com/getsentry/sentry-react-native/pull/6540)) When enabled, the iOS SDK sends `MXDiskWriteExceptionDiagnostic`, `MXCPUExceptionDiagnostic` and `MXHangDiagnostic` reports to Sentry. Requires iOS 15 or later and is disabled by default. MetricKit hang diagnostics are reported by the operating system and are separate from the app hangs captured by `enableAppHangTracking`, so enabling both can result in the same hang being reported twice. diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt index 1416b4de62..2b42b0b90e 100644 --- a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt @@ -347,6 +347,59 @@ class RNSentryStartTest { ) } + @Test + fun `when enableNdkAppHangTracking is true, NDK app hang tracking is enabled`() { + val rnOptions = JavaOnlyMap.of("enableNdkAppHangTracking", true) + val options = SentryAndroidOptions() + + RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger) + + assertTrue("NDK app hang tracking should be enabled", options.isEnableNdkAppHangTracking) + } + + @Test + fun `when enableNdkAppHangTracking is false, NDK app hang tracking is disabled`() { + val rnOptions = JavaOnlyMap.of("enableNdkAppHangTracking", false) + val options = SentryAndroidOptions() + + RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger) + + assertFalse("NDK app hang tracking should be disabled", options.isEnableNdkAppHangTracking) + } + + @Test + fun `when enableNdkAppHangTracking is not set, it remains at default (disabled)`() { + val rnOptions = JavaOnlyMap() + val options = SentryAndroidOptions() + + RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger) + + assertFalse( + "NDK app hang tracking should be disabled by default", + options.isEnableNdkAppHangTracking, + ) + } + + @Test + fun `when ndkAppHangTimeoutIntervalMillis is set, the timeout interval is applied`() { + val rnOptions = JavaOnlyMap.of("ndkAppHangTimeoutIntervalMillis", 3000) + val options = SentryAndroidOptions() + + RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger) + + assertEquals(3000L, options.ndkAppHangTimeoutIntervalMillis) + } + + @Test + fun `when ndkAppHangTimeoutIntervalMillis is not set, it remains at default (5000)`() { + val rnOptions = JavaOnlyMap() + val options = SentryAndroidOptions() + + RNSentryStart.getSentryAndroidOptions(options, rnOptions, logger) + + assertEquals(5000L, options.ndkAppHangTimeoutIntervalMillis) + } + @Test fun `network detail replay options are forwarded to the native replay options`() { val mobileReplayOptions = diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java index 0842cb41c2..2ee396b239 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -202,6 +202,13 @@ static void getSentryAndroidOptions( if (rnOptions.hasKey("enableAnrFingerprinting")) { options.setEnableAnrFingerprinting(rnOptions.getBoolean("enableAnrFingerprinting")); } + if (rnOptions.hasKey("enableNdkAppHangTracking")) { + options.setEnableNdkAppHangTracking(rnOptions.getBoolean("enableNdkAppHangTracking")); + } + if (rnOptions.hasKey("ndkAppHangTimeoutIntervalMillis")) { + options.setNdkAppHangTimeoutIntervalMillis( + rnOptions.getInt("ndkAppHangTimeoutIntervalMillis")); + } if (rnOptions.hasKey("spotlight")) { if (rnOptions.getType("spotlight") == ReadableType.Boolean) { options.setEnableSpotlight(rnOptions.getBoolean("spotlight")); diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 701296cab2..2452cdc88c 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -196,6 +196,29 @@ export interface BaseReactNativeOptions { */ appHangTimeoutInterval?: number; + /** + * When enabled, the SDK uses sentry-native's heartbeat-based app-hang detection + * to track when the application stops responding for a specific amount of time + * defined by the `ndkAppHangTimeoutIntervalMillis` option. + * + * This is independent of the JVM-based ANR detection and requires NDK to be enabled. + * + * @default false + * @platform android + */ + enableNdkAppHangTracking?: boolean; + + /** + * The minimum amount of time in milliseconds an app should be unresponsive to be + * classified as an App Hang when using NDK app-hang detection. + * + * Only has an effect if `enableNdkAppHangTracking` is `true`. + * + * @default 5000 + * @platform android + */ + ndkAppHangTimeoutIntervalMillis?: number; + /** * Use this feature to enable the Sentry MetricKit integration. *