diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e269f898..22108d883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- The SDK automatically subscribes to and leaves breadcrumbs for `Application.lowMemory` events ([#2406](https://github.com/getsentry/sentry-unity/pull/2406)) - The SDK now also reports the currently allocated memory when reporting an event or transaction. ([#2398](https://github.com/getsentry/sentry-unity/pull/2398)) - The SDK defaults `EnvironmentUser` to sensible values based on the current platform. This is still guarded by the `SendDefaultPII` flag. ([#2402](https://github.com/getsentry/sentry-unity/pull/2402)) diff --git a/src/Sentry.Unity/Integrations/IApplication.cs b/src/Sentry.Unity/Integrations/IApplication.cs index 876cbd279..805c63543 100644 --- a/src/Sentry.Unity/Integrations/IApplication.cs +++ b/src/Sentry.Unity/Integrations/IApplication.cs @@ -7,6 +7,7 @@ namespace Sentry.Unity.Integrations; internal interface IApplication { event Application.LogCallback LogMessageReceived; + event Action LowMemory; event Action Quitting; string ActiveSceneName { get; } bool IsEditor { get; } @@ -28,10 +29,12 @@ public sealed class ApplicationAdapter : IApplication private ApplicationAdapter() { Application.logMessageReceivedThreaded += OnLogMessageReceived; + Application.lowMemory += OnLowMemory; Application.quitting += OnQuitting; } public event Application.LogCallback? LogMessageReceived; + public event Action? LowMemory; public event Action? Quitting; @@ -54,6 +57,9 @@ private ApplicationAdapter() private void OnLogMessageReceived(string condition, string stackTrace, LogType type) => LogMessageReceived?.Invoke(condition, stackTrace, type); + private void OnLowMemory() + => LowMemory?.Invoke(); + private void OnQuitting() => Quitting?.Invoke(); } diff --git a/src/Sentry.Unity/Integrations/LowMemoryIntegration.cs b/src/Sentry.Unity/Integrations/LowMemoryIntegration.cs new file mode 100644 index 000000000..4dfadfbb4 --- /dev/null +++ b/src/Sentry.Unity/Integrations/LowMemoryIntegration.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Sentry.Integrations; + +namespace Sentry.Unity.Integrations; + +internal class LowMemoryIntegration : ISdkIntegration +{ + private static readonly Dictionary lowMemoryData = new() { { "action", "LOW_MEMORY" } }; + + private IHub _hub = null!; + private IApplication _application; + + public LowMemoryIntegration(IApplication? application = null) + { + _application = application ?? ApplicationAdapter.Instance; + } + + public void Register(IHub hub, SentryOptions options) + { + _hub = hub; + + _application.LowMemory += () => + { + if (!_hub.IsEnabled) + { + return; + } + + hub.AddBreadcrumb(new Breadcrumb( + message: "Low memory", + type: "system", + data: lowMemoryData, + category: "device.event", + level: BreadcrumbLevel.Warning)); + }; + } +} diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 79f57584e..5633d0bcb 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -360,6 +360,7 @@ internal SentryUnityOptions(IApplication? application = null, AddIntegration(new SceneManagerTracingIntegration()); AddIntegration(new LifeCycleIntegration(behaviour)); AddIntegration(new TraceGenerationIntegration(behaviour)); + AddIntegration(new LowMemoryIntegration()); AddExceptionFilter(new UnityBadGatewayExceptionFilter()); AddExceptionFilter(new UnityWebExceptionFilter()); diff --git a/test/Sentry.Unity.Tests/LowMemoryIntegrationTests.cs b/test/Sentry.Unity.Tests/LowMemoryIntegrationTests.cs new file mode 100644 index 000000000..2fb77976e --- /dev/null +++ b/test/Sentry.Unity.Tests/LowMemoryIntegrationTests.cs @@ -0,0 +1,72 @@ +using System.Linq; +using NUnit.Framework; +using Sentry.Unity.Integrations; +using Sentry.Unity.Tests.Stubs; + +namespace Sentry.Unity.Tests; + +public class LowMemoryIntegrationTests +{ + private class Fixture + { + public TestApplication Application { get; set; } = new(); + public TestHub Hub { get; set; } = new(); + public SentryUnityOptions Options { get; set; } = new(); + + public LowMemoryIntegration GetSut() => new(Application); + } + + private Fixture _fixture = null!; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + } + + [Test] + public void LowMemory_DisabledHub_NoBreadcrumbAdded() + { + _fixture.Hub = new TestHub(false); + var sut = _fixture.GetSut(); + + sut.Register(_fixture.Hub, _fixture.Options); + _fixture.Application.OnLowMemory(); + + Assert.Zero(_fixture.Hub.ConfigureScopeCalls.Count); + } + + [Test] + public void LowMemory_EnabledHub_BreadcrumbAdded() + { + var sut = _fixture.GetSut(); + + sut.Register(_fixture.Hub, _fixture.Options); + _fixture.Application.OnLowMemory(); + + var configureScope = _fixture.Hub.ConfigureScopeCalls.Single(); + var scope = new Scope(_fixture.Options); + configureScope(scope); + var actualCrumb = scope.Breadcrumbs.Single(); + + Assert.AreEqual("Low memory", actualCrumb.Message); + Assert.AreEqual("device.event", actualCrumb.Category); + Assert.AreEqual("system", actualCrumb.Type); + Assert.AreEqual(BreadcrumbLevel.Warning, actualCrumb.Level); + Assert.NotNull(actualCrumb.Data); + Assert.AreEqual("LOW_MEMORY", actualCrumb.Data?["action"]); + } + + [Test] + public void LowMemory_MultipleTriggers_MultipleBreadcrumbsAdded() + { + var sut = _fixture.GetSut(); + + sut.Register(_fixture.Hub, _fixture.Options); + _fixture.Application.OnLowMemory(); + _fixture.Application.OnLowMemory(); + _fixture.Application.OnLowMemory(); + + Assert.AreEqual(3, _fixture.Hub.ConfigureScopeCalls.Count); + } +} diff --git a/test/SharedClasses/TestApplication.cs b/test/SharedClasses/TestApplication.cs index c8551297d..dfde1a52d 100644 --- a/test/SharedClasses/TestApplication.cs +++ b/test/SharedClasses/TestApplication.cs @@ -25,6 +25,7 @@ public TestApplication( } public event Application.LogCallback? LogMessageReceived; + public event Action? LowMemory; public event Action? Quitting; public string ActiveSceneName => "TestSceneName"; public bool IsEditor { get; set; } @@ -34,8 +35,10 @@ public TestApplication( public string UnityVersion { get; set; } public string PersistentDataPath { get; set; } public RuntimePlatform Platform { get; set; } - private void OnQuitting() => Quitting?.Invoke(); private void OnLogMessageReceived(string condition, string stacktrace, LogType type) => LogMessageReceived?.Invoke(condition, stacktrace, type); + + public void OnLowMemory() => LowMemory?.Invoke(); + private void OnQuitting() => Quitting?.Invoke(); }