Skip to content

Commit 3de869c

Browse files
authored
feat: ios native app hang capture (#2679)
1 parent 87a73b9 commit 3de869c

13 files changed

Lines changed: 129 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
### Features
1111

12-
- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671))
12+
- Added `EnableAppHangTracking` (default `true`) and `AppHangTimeout` (default `5s`) to enable app hang detection via the native SDK. Currently effective on iOS through `sentry-cocoa`, which monitors the main thread and produces a stack trace for the hang event. On other platforms this is a no-op until each platform's native hang detection lands. When enabled on iOS, the Unity SDK's C# watchdog is skipped to avoid duplicate reports ([#2679](https://github.com/getsentry/sentry-unity/pull/2679))
13+
- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through the `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671))
1314

1415
### Dependencies
1516

package-dev/Plugins/iOS/SentryNativeBridge.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ void SentryNativeBridgeOptionsSetInt(const void *options, const char *name, int3
6666
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithInt:value];
6767
}
6868

69+
void SentryNativeBridgeOptionsSetDouble(const void *options, const char *name, double value)
70+
{
71+
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
72+
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithDouble:value];
73+
}
74+
6975
void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(const void *options, int32_t min, int32_t max)
7076
{
7177
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;

package-dev/Plugins/iOS/SentryNativeBridgeNoOp.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
void *_Nullable SentryNativeBridgeOptionsNew() { return nil; }
77
void SentryNativeBridgeOptionsSetString(void *options, const char *name, const char *value) { }
88
void SentryNativeBridgeOptionsSetInt(void *options, const char *name, int32_t value) { }
9+
void SentryNativeBridgeOptionsSetDouble(void *options, const char *name, double value) { }
910
void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(void *options, int32_t min, int32_t max) { }
1011
int SentryNativeBridgeStartWithOptions(void *options) { return 0; }
1112

package-dev/Plugins/macOS/SentryNativeBridge.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ void SentryNativeBridgeOptionsSetInt(const void *options, const char *name, int3
121121
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithInt:value];
122122
}
123123

124+
void SentryNativeBridgeOptionsSetDouble(const void *options, const char *name, double value)
125+
{
126+
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;
127+
dictOptions[[NSString stringWithUTF8String:name]] = [NSNumber numberWithDouble:value];
128+
}
129+
124130
void SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange(const void *options, int32_t min, int32_t max)
125131
{
126132
NSMutableDictionary *dictOptions = (__bridge NSMutableDictionary *)options;

samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ MonoBehaviour:
1111
m_EditorHideFlags: 0
1212
m_Script: {fileID: -668357930, guid: 43ec428a58422470fa764bdba9d9bc19, type: 3}
1313
m_Name: SentryOptions
14-
m_EditorClassIdentifier:
14+
m_EditorClassIdentifier:
1515
<Enabled>k__BackingField: 1
1616
<Dsn>k__BackingField: https://e9ee299dbf554dfd930bc5f3c90d5d4b@o447951.ingest.us.sentry.io/4504604988538880
1717
<CaptureInEditor>k__BackingField: 1
@@ -27,8 +27,8 @@ MonoBehaviour:
2727
<AutoAwakeTraces>k__BackingField: 0
2828
<AutoSessionTracking>k__BackingField: 1
2929
<AutoSessionTrackingInterval>k__BackingField: 30000
30-
<ReleaseOverride>k__BackingField:
31-
<EnvironmentOverride>k__BackingField:
30+
<ReleaseOverride>k__BackingField:
31+
<EnvironmentOverride>k__BackingField:
3232
<AttachStacktrace>k__BackingField: 1
3333
<AttachScreenshot>k__BackingField: 1
3434
<ScreenshotQuality>k__BackingField: 1
@@ -61,6 +61,8 @@ MonoBehaviour:
6161
<MaxQueueItems>k__BackingField: 30
6262
<AnrDetectionEnabled>k__BackingField: 0
6363
<AnrTimeout>k__BackingField: 5000
64+
<EnableAppHangTracking>k__BackingField: 1
65+
<AppHangTimeout>k__BackingField: 5000
6466
<CaptureFailedRequests>k__BackingField: 1
6567
<FailedRequestStatusCodes>k__BackingField: f401000057020000
6668
<FilterBadGatewayExceptions>k__BackingField: 1

src/Sentry.Unity.Editor.iOS/NativeOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ internal static string Generate(SentryUnityOptions options)
3030
@""maxBreadcrumbs"": @{options.MaxBreadcrumbs},
3131
@""maxCacheItems"": @{options.MaxCacheItems},
3232
@""enableAutoSessionTracking"": @NO,
33-
@""enableAppHangTracking"": @NO,
33+
@""enableAppHangTracking"": @{ToObjCString(options.EnableAppHangTracking)},
34+
@""appHangTimeoutInterval"": @{options.AppHangTimeout.TotalSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture)},
3435
@""enableCaptureFailedRequests"": @{ToObjCString(options.CaptureFailedRequests)},
3536
@""failedRequestStatusCodes"" : @[{failedRequestStatusCodesArray}],
3637
@""sendDefaultPii"" : @{ToObjCString(options.SendDefaultPii)},

src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,40 @@ internal static void Display(ScriptableSentryUnityOptions options, SentryCliOpti
3939
EditorGUILayout.Space();
4040

4141
{
42-
options.AnrDetectionEnabled = EditorGUILayout.BeginToggleGroup(
43-
new GUIContent("ANR Detection", "Whether the SDK should report 'Application Not " +
44-
"Responding' events."),
42+
GUILayout.Label("C# Watchdog", EditorStyles.boldLabel);
43+
44+
options.AnrDetectionEnabled = EditorGUILayout.Toggle(
45+
new GUIContent("Enable", "Whether the SDK should run the C# main-thread watchdog " +
46+
"to report 'Application Not Responding' events."),
4547
options.AnrDetectionEnabled);
46-
EditorGUI.indentLevel++;
4748

4849
options.AnrTimeout = EditorGUILayout.IntField(
49-
new GUIContent("Timeout [ms]",
50+
new GUIContent("Watchdog Timeout [ms]",
5051
"The duration in [ms] for how long the game has to be unresponsive " +
51-
"before an ANR event is reported.\nDefault: 5000ms"),
52+
"before the C# watchdog reports an ANR event.\nDefault: 5000ms"),
5253
options.AnrTimeout);
5354
options.AnrTimeout = Math.Max(0, options.AnrTimeout);
55+
}
5456

55-
EditorGUI.indentLevel--;
56-
EditorGUILayout.EndToggleGroup();
57+
EditorGUILayout.Space();
58+
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
59+
EditorGUILayout.Space();
60+
61+
{
62+
GUILayout.Label("App Hang Tracking", EditorStyles.boldLabel);
63+
64+
options.EnableAppHangTracking = EditorGUILayout.Toggle(
65+
new GUIContent("Enable",
66+
"Enables app hang detection via the native SDK. Currently effective on iOS only; " +
67+
"no-op on other platforms until each platform's native hang detection lands."),
68+
options.EnableAppHangTracking);
69+
70+
options.AppHangTimeout = EditorGUILayout.IntField(
71+
new GUIContent("App Hang Timeout [ms]",
72+
"The duration in [ms] for how long the main thread has to be blocked " +
73+
"before an app hang is reported.\nDefault: 5000ms"),
74+
options.AppHangTimeout);
75+
options.AppHangTimeout = Math.Max(0, options.AppHangTimeout);
5776
}
5877

5978
EditorGUILayout.Space();

src/Sentry.Unity.Editor/ScriptableSentryUnityOptionsEditor.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,13 @@ public override void OnInspectorGUI()
6868
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
6969
EditorGUILayout.Space();
7070

71-
EditorGUILayout.LabelField("Application Not Responding", EditorStyles.boldLabel);
72-
EditorGUILayout.Toggle("Enable ANR Detection", options.AnrDetectionEnabled);
73-
EditorGUILayout.IntField("ANR Timeout [ms]", options.AnrTimeout);
71+
EditorGUILayout.LabelField("C# Watchdog", EditorStyles.boldLabel);
72+
EditorGUILayout.Toggle("Enable", options.AnrDetectionEnabled);
73+
EditorGUILayout.IntField("Watchdog Timeout [ms]", options.AnrTimeout);
74+
75+
EditorGUILayout.LabelField("App Hang Tracking", EditorStyles.boldLabel);
76+
EditorGUILayout.Toggle("Enable", options.EnableAppHangTracking);
77+
EditorGUILayout.IntField("App Hang Timeout [ms]", options.AppHangTimeout);
7478

7579
EditorGUILayout.Space();
7680
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);

src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ public static bool Init(SentryUnityOptions options)
6666
// See https://github.com/getsentry/sentry-unity/issues/1658
6767
OptionsSetInt(cOptions, "enableNetworkBreadcrumbs", 0);
6868

69+
Logger?.LogDebug("Setting EnableAppHangTracking: {0}", options.EnableAppHangTracking);
70+
OptionsSetInt(cOptions, "enableAppHangTracking", options.EnableAppHangTracking ? 1 : 0);
71+
72+
Logger?.LogDebug("Setting AppHangTimeoutInterval: {0}s", options.AppHangTimeout.TotalSeconds);
73+
OptionsSetDouble(cOptions, "appHangTimeoutInterval", options.AppHangTimeout.TotalSeconds);
74+
6975
Logger?.LogDebug("Setting EnableWatchdogTerminationTracking: {0}", options.IosWatchdogTerminationIntegrationEnabled);
7076
OptionsSetInt(cOptions, "enableWatchdogTerminationTracking", options.IosWatchdogTerminationIntegrationEnabled ? 1 : 0);
7177

@@ -103,6 +109,9 @@ public static bool Init(SentryUnityOptions options)
103109
[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetInt")]
104110
private static extern void OptionsSetInt(IntPtr options, string name, int value);
105111

112+
[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetDouble")]
113+
private static extern void OptionsSetDouble(IntPtr options, string name, double value);
114+
106115
[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange")]
107116
private static extern void OptionsAddFailedRequestStatusCodeRange(IntPtr options, int min, int max);
108117

src/Sentry.Unity.iOS/SentryNativeCocoa.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
4343
}
4444

4545
options.ScopeObserver = new NativeScopeObserver("iOS", options);
46+
47+
if (options.EnableAppHangTracking)
48+
{
49+
Logger?.LogDebug("Disabling the C# ANR watchdog - sentry-cocoa handles app hang detection.");
50+
options.DisableAnrIntegration();
51+
}
52+
4653
}
4754
else
4855
{

0 commit comments

Comments
 (0)