diff --git a/CHANGELOG.md b/CHANGELOG.md index 2349b2a70..b56558346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - Preventing `LoggingIntegration` from registering multiple times ([#1178](https://github.com/getsentry/sentry-unity/pull/1178)) - Fixed the logging integration only capturing tags and missing the message ([#1150](https://github.com/getsentry/sentry-unity/pull/1150)) +### Features + +- Added ANR options to the editor window and made ANR timeout accessible on the options object ([#1181](https://github.com/getsentry/sentry-unity/pull/1181)) + ### Dependencies - Bump Java SDK from v6.12.1 to v6.14.0 ([#1156](https://github.com/getsentry/sentry-unity/pull/1156), [#1171](https://github.com/getsentry/sentry-unity/pull/1171), [#1184](https://github.com/getsentry/sentry-unity/pull/1184)) diff --git a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset index efebff3ba..a9ed0edc9 100644 --- a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset +++ b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset @@ -47,6 +47,8 @@ MonoBehaviour: k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 + k__BackingField: 1 + k__BackingField: 5000 k__BackingField: {fileID: 11400000, guid: 407b4d5f2fef2c845bf2a3dcdf6b00fb, type: 2} k__BackingField: {fileID: 11400000, guid: 6bcc81a646c08ce439ab03805d477458, diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs index 4eb696357..eba5819ae 100644 --- a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs +++ b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs @@ -1,3 +1,4 @@ +using System; using UnityEditor; using UnityEngine; @@ -7,40 +8,63 @@ internal static class AdvancedTab { internal static void Display(ScriptableSentryUnityOptions options, SentryCliOptions? cliOptions) { - options.Debug = EditorGUILayout.BeginToggleGroup( - new GUIContent("Enable Debug Output", "Whether the Sentry SDK should print its " + - "diagnostic logs to the console."), - options.Debug); - - options.DebugOnlyInEditor = EditorGUILayout.Toggle( - new GUIContent("Only In Editor", "Only print logs when in the editor. Development " + - "builds of the player will not include Sentry's SDK diagnostics."), - options.DebugOnlyInEditor); + { + options.Debug = EditorGUILayout.BeginToggleGroup( + new GUIContent("Enable Debug Output", "Whether the Sentry SDK should print its " + + "diagnostic logs to the console."), + options.Debug); + + options.DebugOnlyInEditor = EditorGUILayout.Toggle( + new GUIContent("Only In Editor", "Only print logs when in the editor. Development " + + "builds of the player will not include Sentry's SDK diagnostics."), + options.DebugOnlyInEditor); + + options.DiagnosticLevel = (SentryLevel)EditorGUILayout.EnumPopup( + new GUIContent("Verbosity Level", "The minimum level allowed to be printed to the console. " + + "Log messages with a level below this level are dropped."), + options.DiagnosticLevel); + + EditorGUILayout.EndToggleGroup(); + } - options.DiagnosticLevel = (SentryLevel)EditorGUILayout.EnumPopup( - new GUIContent("Verbosity Level", "The minimum level allowed to be printed to the console. " + - "Log messages with a level below this level are dropped."), - options.DiagnosticLevel); + EditorGUILayout.Space(); + EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); + EditorGUILayout.Space(); - EditorGUILayout.EndToggleGroup(); + { + options.AutoSessionTracking = EditorGUILayout.BeginToggleGroup( + new GUIContent("Auto Session Tracking", "Whether the SDK should start and end sessions " + + "automatically. If the timeout is reached the old session will" + + "be ended and a new one started."), + options.AutoSessionTracking); + + options.AutoSessionTrackingInterval = EditorGUILayout.IntField( + new GUIContent("Session Timeout [ms]", "The duration of time a session can stay paused " + + "(i.e. the application has been put in the background) before " + + "it is considered ended."), + options.AutoSessionTrackingInterval); + options.AutoSessionTrackingInterval = Mathf.Max(0, options.AutoSessionTrackingInterval); + EditorGUILayout.EndToggleGroup(); + } EditorGUILayout.Space(); EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); EditorGUILayout.Space(); - options.AutoSessionTracking = EditorGUILayout.BeginToggleGroup( - new GUIContent("Auto Session Tracking", "Whether the SDK should start and end sessions " + - "automatically. If the timeout is reached the old session will" + - "be ended and a new one started."), - options.AutoSessionTracking); - - options.AutoSessionTrackingInterval = EditorGUILayout.IntField( - new GUIContent("Session Timeout [ms]", "The duration of time a session can stay paused " + - "(i.e. the application has been put in the background) before " + - "it is considered ended."), - options.AutoSessionTrackingInterval); - options.AutoSessionTrackingInterval = Mathf.Max(0, options.AutoSessionTrackingInterval); - EditorGUILayout.EndToggleGroup(); + { + options.AnrDetectionEnabled = EditorGUILayout.BeginToggleGroup( + new GUIContent("ANR Detection", "Whether the SDK should report 'Application Not " + + "Responding' events."), + options.AnrDetectionEnabled); + + options.AnrTimeout = EditorGUILayout.IntField( + new GUIContent("ANR Timeout [ms]", "The duration in [ms] for how long the game has to be unresponsive " + + "before an ANR event is reported.\nDefault: 5000ms"), + options.AnrTimeout); + options.AnrTimeout = Math.Max(0, options.AnrTimeout); + ; + EditorGUILayout.EndToggleGroup(); + } EditorGUILayout.Space(); EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); @@ -48,46 +72,50 @@ internal static void Display(ScriptableSentryUnityOptions options, SentryCliOpti GUILayout.Label("Native Support", EditorStyles.boldLabel); - options.IosNativeSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("iOS Native Support", "Whether to enable Native iOS support to capture" + - "errors written in languages such as Objective-C, Swift, C and C++."), - options.IosNativeSupportEnabled); - - options.AndroidNativeSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("Android Native Support", "Whether to enable Native Android support to " + - "capture errors written in languages such as Java, Kotlin, C and C++."), - options.AndroidNativeSupportEnabled); - - options.WindowsNativeSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("Windows Native Support", "Whether to enable native crashes support on Windows."), - options.WindowsNativeSupportEnabled); - - options.MacosNativeSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("macOS Native Support", "Whether to enable native crashes support on macOS."), - options.MacosNativeSupportEnabled); - - options.LinuxNativeSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("Linux Native Support", "Whether to enable native crashes support on Linux."), - options.LinuxNativeSupportEnabled); + { + options.IosNativeSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("iOS Native Support", "Whether to enable Native iOS support to capture" + + "errors written in languages such as Objective-C, Swift, C and C++."), + options.IosNativeSupportEnabled); + + options.AndroidNativeSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("Android Native Support", "Whether to enable Native Android support to " + + "capture errors written in languages such as Java, Kotlin, C and C++."), + options.AndroidNativeSupportEnabled); + + options.WindowsNativeSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("Windows Native Support", "Whether to enable native crashes support on Windows."), + options.WindowsNativeSupportEnabled); + + options.MacosNativeSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("macOS Native Support", "Whether to enable native crashes support on macOS."), + options.MacosNativeSupportEnabled); + + options.LinuxNativeSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("Linux Native Support", "Whether to enable native crashes support on Linux."), + options.LinuxNativeSupportEnabled); + } EditorGUILayout.Space(); EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); EditorGUILayout.Space(); - options.Il2CppLineNumberSupportEnabled = EditorGUILayout.Toggle( - new GUIContent("IL2CPP line numbers", "Whether the SDK should try to to provide line " + - "numbers for exceptions in IL2CPP builds."), - options.Il2CppLineNumberSupportEnabled); - - if (options.Il2CppLineNumberSupportEnabled) { - if (!SentryUnityVersion.IsNewerOrEqualThan("2020.3")) - { - EditorGUILayout.HelpBox("The IL2CPP line number feature is supported from Unity version 2020.3 or newer and 2021.3 or newer onwards", MessageType.Warning); - } - else if (cliOptions is not null && !cliOptions.IsValid(null, EditorUserBuildSettings.development)) + options.Il2CppLineNumberSupportEnabled = EditorGUILayout.Toggle( + new GUIContent("IL2CPP line numbers", "Whether the SDK should try to to provide line " + + "numbers for exceptions in IL2CPP builds."), + options.Il2CppLineNumberSupportEnabled); + + if (options.Il2CppLineNumberSupportEnabled) { - EditorGUILayout.HelpBox("The IL2CPP line number support relies on the Debug Symbol Upload to be properly set up.", MessageType.Error); + if (!SentryUnityVersion.IsNewerOrEqualThan("2020.3")) + { + EditorGUILayout.HelpBox("The IL2CPP line number feature is supported from Unity version 2020.3 or newer and 2021.3 or newer onwards", MessageType.Warning); + } + else if (cliOptions is not null && !cliOptions.IsValid(null, EditorUserBuildSettings.development)) + { + EditorGUILayout.HelpBox("The IL2CPP line number support relies on the Debug Symbol Upload to be properly set up.", MessageType.Error); + } } } } diff --git a/src/Sentry.Unity.Editor/ScriptableSentryUnityOptionsEditor.cs b/src/Sentry.Unity.Editor/ScriptableSentryUnityOptionsEditor.cs index f5920ce08..d183dcc36 100644 --- a/src/Sentry.Unity.Editor/ScriptableSentryUnityOptionsEditor.cs +++ b/src/Sentry.Unity.Editor/ScriptableSentryUnityOptionsEditor.cs @@ -59,6 +59,14 @@ public override void OnInspectorGUI() EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); EditorGUILayout.Space(); + EditorGUILayout.LabelField("Application Not Responding", EditorStyles.boldLabel); + EditorGUILayout.Toggle("Enable ANR Detection", options.AnrDetectionEnabled); + EditorGUILayout.IntField("ANR Timeout [ms]", options.AnrTimeout); + + EditorGUILayout.Space(); + EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); + EditorGUILayout.Space(); + EditorGUILayout.Toggle("iOS Native Support", options.IosNativeSupportEnabled); EditorGUILayout.Toggle("Android Native Support", options.AndroidNativeSupportEnabled); EditorGUILayout.Toggle("Windows Native Support", options.WindowsNativeSupportEnabled); diff --git a/src/Sentry.Unity/Integrations/AnrIntegration.cs b/src/Sentry.Unity/Integrations/AnrIntegration.cs index b8e736fdc..f808221a6 100644 --- a/src/Sentry.Unity/Integrations/AnrIntegration.cs +++ b/src/Sentry.Unity/Integrations/AnrIntegration.cs @@ -28,11 +28,15 @@ public void Register(IHub hub, SentryOptions sentryOptions) { if (options.MultiThreading) { - Watchdog = new AnrWatchDogMultiThreaded(options.DiagnosticLogger, _monoBehaviour); + Watchdog = new AnrWatchDogMultiThreaded(options.DiagnosticLogger, + _monoBehaviour, + options.AnrTimeout); } else { - Watchdog = new AnrWatchDogSingleThreaded(options.DiagnosticLogger, _monoBehaviour); + Watchdog = new AnrWatchDogSingleThreaded(options.DiagnosticLogger, + _monoBehaviour, + options.AnrTimeout); } } } @@ -50,11 +54,11 @@ internal abstract class AnrWatchDog internal event EventHandler OnApplicationNotResponding = delegate { }; protected bool Paused { get; private set; } = false; - internal AnrWatchDog(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, int detectionTimeoutMilliseconds = 5000) + internal AnrWatchDog(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, TimeSpan detectionTimeout) { MonoBehaviour = monoBehaviour; Logger = logger; - DetectionTimeoutMs = detectionTimeoutMilliseconds; + DetectionTimeoutMs = detectionTimeout.Milliseconds; SleepIntervalMs = Math.Max(1, DetectionTimeoutMs / 5); MonoBehaviour.ApplicationPausing += () => Paused = true; @@ -85,8 +89,8 @@ internal class AnrWatchDogMultiThreaded : AnrWatchDog private bool _stop; private readonly Thread _thread = null!; - internal AnrWatchDogMultiThreaded(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, int detectionTimeoutMilliseconds = 5000) - : base(logger, monoBehaviour, detectionTimeoutMilliseconds) + internal AnrWatchDogMultiThreaded(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, TimeSpan detectionTimeout) + : base(logger, monoBehaviour, detectionTimeout) { _thread = new Thread(Run) { @@ -164,8 +168,8 @@ internal class AnrWatchDogSingleThreaded : AnrWatchDog private readonly Stopwatch _watch = new(); private bool _stop; - internal AnrWatchDogSingleThreaded(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, int detectionTimeoutMilliseconds = 5000) - : base(logger, monoBehaviour, detectionTimeoutMilliseconds) + internal AnrWatchDogSingleThreaded(IDiagnosticLogger? logger, SentryMonoBehaviour monoBehaviour, TimeSpan detectionTimeout) + : base(logger, monoBehaviour, detectionTimeout) { // Check the UI status periodically by running a coroutine on the UI thread and checking the elapsed time _watch.Start(); diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index bf8773190..309219382 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -69,11 +69,16 @@ public static string GetConfigPath(string? notDefaultConfigName = null) [field: SerializeField] public float SampleRate { get; set; } = 1.0f; [field: SerializeField] public int ShutdownTimeout { get; set; } = 2000; [field: SerializeField] public int MaxQueueItems { get; set; } = 30; + + [field: SerializeField] public bool AnrDetectionEnabled { get; set; } = true; + [field: SerializeField] public int AnrTimeout { get; set; } = (int)TimeSpan.FromSeconds(5).TotalMilliseconds; + [field: SerializeField] public bool IosNativeSupportEnabled { get; set; } = true; [field: SerializeField] public bool AndroidNativeSupportEnabled { get; set; } = true; [field: SerializeField] public bool WindowsNativeSupportEnabled { get; set; } = true; [field: SerializeField] public bool MacosNativeSupportEnabled { get; set; } = true; [field: SerializeField] public bool LinuxNativeSupportEnabled { get; set; } = true; + [field: SerializeField] public bool Il2CppLineNumberSupportEnabled { get; set; } = true; [field: SerializeField] public SentryRuntimeOptionsConfiguration? OptionsConfiguration { get; set; } @@ -131,6 +136,7 @@ internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityIn // need to set it here directly. Debug = ShouldDebug(application.IsEditor && !isBuilding), DiagnosticLevel = DiagnosticLevel, + AnrTimeout = TimeSpan.FromMilliseconds(AnrTimeout), IosNativeSupportEnabled = IosNativeSupportEnabled, AndroidNativeSupportEnabled = AndroidNativeSupportEnabled, WindowsNativeSupportEnabled = WindowsNativeSupportEnabled, @@ -197,6 +203,11 @@ internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityIn { options.AddIl2CppExceptionProcessor(unityInfo); } + + if (!AnrDetectionEnabled) + { + options.DisableAnrIntegration(); + } } return options; diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 653cc1033..9ecd2b975 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -88,6 +88,11 @@ public sealed class SentryUnityOptions : SentryOptions /// public Dictionary AddBreadcrumbsForLogType { get; set; } + /// + /// The duration in [ms] for how long the game has to be unresponsive before an ANR event is reported. + /// + public TimeSpan AnrTimeout { get; set; } = TimeSpan.FromSeconds(5); + /// /// Whether the SDK should add native support for iOS /// diff --git a/test/Sentry.Unity.Tests/AnrDetectionTests.cs b/test/Sentry.Unity.Tests/AnrDetectionTests.cs index 07244698a..e393f8b58 100644 --- a/test/Sentry.Unity.Tests/AnrDetectionTests.cs +++ b/test/Sentry.Unity.Tests/AnrDetectionTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Diagnostics; using System.Threading; @@ -11,13 +12,12 @@ namespace Sentry.Unity.Tests { public class AnrDetectionTests { - private const int Timeout = 500; + private readonly TimeSpan _timeout = TimeSpan.FromSeconds(0.5); private readonly IDiagnosticLogger _logger = new TestLogger(forwardToUnityLog: true); private GameObject _gameObject = null!; private SentryMonoBehaviour _monoBehaviour = null!; private AnrWatchDog _sut = null!; - [SetUp] public void SetUp() { @@ -30,10 +30,10 @@ public void SetUp() private AnrWatchDog CreateWatchDog(bool multiThreaded) { - UnityEngine.Debug.Log($"Preparing ANR watchdog: timeout={Timeout} multiThreaded={multiThreaded}"); + UnityEngine.Debug.Log($"Preparing ANR watchdog: timeout={_timeout} multiThreaded={multiThreaded}"); return multiThreaded - ? new AnrWatchDogMultiThreaded(_logger, _monoBehaviour, Timeout) - : new AnrWatchDogSingleThreaded(_logger, _monoBehaviour, Timeout); + ? new AnrWatchDogMultiThreaded(_logger, _monoBehaviour, _timeout) + : new AnrWatchDogSingleThreaded(_logger, _monoBehaviour, _timeout); } // Needed for [UnityTest] - IEnumerator return value @@ -49,7 +49,7 @@ public IEnumerator DetectsStuckUI([ValueSource(nameof(MultiThreadingTestValues)) // Thread.Sleep blocks the UI thread var watch = Stopwatch.StartNew(); - while (watch.ElapsedMilliseconds < Timeout * 2 && arn is null) + while (watch.Elapsed < TimeSpan.FromTicks(_timeout.Ticks * 2) && arn is null) { Thread.Sleep(10); } @@ -58,7 +58,7 @@ public IEnumerator DetectsStuckUI([ValueSource(nameof(MultiThreadingTestValues)) if (!multiThreaded) { watch.Restart(); - while (watch.ElapsedMilliseconds < Timeout && arn is null) + while (watch.Elapsed < _timeout && arn is null) { yield return null; } @@ -77,7 +77,7 @@ public IEnumerator DoesntReportWorkingUI([ValueSource(nameof(MultiThreadingTestV // yield WaitForSeconds doesn't block the UI thread var watch = Stopwatch.StartNew(); - while (watch.ElapsedMilliseconds < Timeout * 3) + while (watch.Elapsed < TimeSpan.FromTicks(_timeout.Ticks * 3)) { yield return new WaitForSeconds(0.01f); } @@ -95,7 +95,7 @@ public void DoesntReportShortlyStuckUI(bool multiThreaded) _sut.OnApplicationNotResponding += (_, e) => arn = e; // Thread.Sleep blocks the UI thread - Thread.Sleep(Timeout / 2); + Thread.Sleep(TimeSpan.FromTicks(_timeout.Ticks / 2)); Assert.IsNull(arn); } @@ -111,13 +111,13 @@ public IEnumerator DoesntReportWhilePaused([ValueSource(nameof(MultiThreadingTes _monoBehaviour.UpdatePauseStatus(true); // Thread.Sleep blocks the UI thread - Thread.Sleep(Timeout * 2); + Thread.Sleep(TimeSpan.FromTicks(_timeout.Ticks * 2)); // We need to let the single-threaded watchdog populate `arn` after UI became responsive again. if (!multiThreaded) { var watch = Stopwatch.StartNew(); - while (watch.ElapsedMilliseconds < Timeout && arn is null) + while (watch.Elapsed < _timeout && arn is null) { yield return null; }