diff --git a/Android/BacktraceAttributes.java b/Android/BacktraceAttributes.java deleted file mode 100644 index 7af973c8..00000000 --- a/Android/BacktraceAttributes.java +++ /dev/null @@ -1,166 +0,0 @@ -package backtrace.io.backtrace_unity_android_plugin; - -import android.content.Context; -import android.os.Build; -import android.os.PowerManager; -import android.util.Log; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -public class BacktraceAttributes { - - private Context context; - private final static transient String LOG_TAG = BacktraceAttributes.class.getSimpleName(); - private static Map _attributeMapping = new HashMap(); - static { - _attributeMapping.put("FDSize", "descriptor.count"); - _attributeMapping.put("VmPeak", "vm.vma.peak"); - _attributeMapping.put("VmSize", "vm.vma.size"); - _attributeMapping.put("VmLck", "vm.locked.size"); - _attributeMapping.put("VmHWM", "vm.rss.peak"); - _attributeMapping.put("VmRSS", "vm.rss.size"); - _attributeMapping.put("VmStk", "vm.stack.size"); - _attributeMapping.put("VmData", "vm.data"); - _attributeMapping.put("VmExe", "vm.exe"); - _attributeMapping.put("VmLib", "vm.shared.size"); - _attributeMapping.put("VmPTE", "vm.pte.size"); - _attributeMapping.put("VmSwap", "vm.swap.size"); - - _attributeMapping.put("State", "state"); - - - _attributeMapping.put("voluntary_ctxt_switches", "sched.cs.voluntary"); - _attributeMapping.put("nonvoluntary_ctxt_switches", "sched.cs.involuntary"); - - _attributeMapping.put("SigPnd", "vm.sigpnd"); - _attributeMapping.put("ShdPnd", "vm.shdpnd"); - _attributeMapping.put("Threads", "vm.threads"); - - _attributeMapping.put("MemTotal", "system.memory.total"); - _attributeMapping.put("MemFree", "system.memory.free"); - _attributeMapping.put("Buffers", "system.memory.buffers"); - _attributeMapping.put("Cached", "system.memory.cached"); - _attributeMapping.put("SwapCached", "system.memory.swap.cached"); - _attributeMapping.put("Active", "system.memory.active"); - _attributeMapping.put("Inactive", "system.memory.inactive"); - _attributeMapping.put("SwapTotal", "system.memory.swap.total"); - _attributeMapping.put("SwapFree", "system.memory.swap.free"); - _attributeMapping.put("Dirty", "system.memory.dirty"); - _attributeMapping.put("Writeback", "system.memory.writeback"); - _attributeMapping.put("Slab", "system.memory.slab"); - _attributeMapping.put("VmallocTotal", "system.memory.vmalloc.total"); - _attributeMapping.put("VmallocUsed", "system.memory.vmalloc.used"); - _attributeMapping.put("VmallocChunk", "system.memory.vmalloc.chunk"); - } - - public Map GetAttributes(Context unityContext) { - - context = unityContext; - HashMap result = getProcessAttributes(); - result.put("app.storage_used", getAppUsedStorageSize()); - result.put("device.cpu.temperature", getCpuTemperature()); - result.put("device.is_power_saving_mode", Boolean.toString(isPowerSavingMode())); - result.put("culture", Locale.getDefault().getDisplayLanguage()); - result.put("device.sdk", Integer.toString(Build.VERSION.SDK_INT)); - result.put("device.manufacturer", Build.MANUFACTURER); - - result.entrySet().iterator(); - return result; - } - - - private boolean isPowerSavingMode() { - if (Build.VERSION.SDK_INT < 21) { - return false; - } - PowerManager powerManager = (PowerManager) this.context.getSystemService(Context - .POWER_SERVICE); - return powerManager.isPowerSaveMode(); - } - - public HashMap getProcessAttributes() { - - HashMap result = new HashMap<>(); - - int processId = android.os.Process.myPid(); - if (processId < 0) { - Log.d(LOG_TAG, "Failed to read process id"); - return result; - } - String processAttributes = String.format("/proc/%d/status", processId); - String memoryAttributes = "/proc/meminfo"; - return readAttributesFromFile( - memoryAttributes, - readAttributesFromFile(processAttributes, result)); - } - - private HashMap readAttributesFromFile(String path, HashMap attributes) { - File file = new File(path); - - StringBuilder text = new StringBuilder(); - try { - BufferedReader br = new BufferedReader(new FileReader(file)); - String line; - - while ((line = br.readLine()) != null) { - String[] entry = line.split(":", 2); - String key = entry[0].trim(); - if(!_attributeMapping.containsKey(key)){ - continue; - } - key = _attributeMapping.get(key); - String value = entry[1].trim(); - if(value.endsWith("kB")){ - value = value.substring(0,value.lastIndexOf('k')).trim(); - } - attributes.put(key, value); - } - br.close(); - } catch (IOException e) { - Log.d(LOG_TAG, "Cannot read process information. Reason:" + e.getMessage()); - attributes.put("parseError", e.getMessage()); - } - - return attributes; - } - - - public String getCpuTemperature() { - Process p; - try { - p = Runtime.getRuntime().exec("cat sys/class/thermal/thermal_zone0/temp"); - p.waitFor(); - BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); - - String line = reader.readLine(); - if (line == null) { - return "0.0"; - } - return Float.toString(Float.parseFloat(line) / 1000.0f); - } catch (Exception e) { - return "0.0"; - } - } - - public String getAppUsedStorageSize() { - long freeSize = 0L; - long totalSize = 0L; - long usedSize = -1L; - try { - Runtime info = Runtime.getRuntime(); - freeSize = info.freeMemory(); - totalSize = info.totalMemory(); - usedSize = totalSize - freeSize; - } catch (Exception e) { - e.printStackTrace(); - } - return Long.toString(usedSize); - } -} diff --git a/Android/BacktraceAttributes.java.meta b/Android/BacktraceAttributes.java.meta deleted file mode 100644 index 1d3fd54e..00000000 --- a/Android/BacktraceAttributes.java.meta +++ /dev/null @@ -1,32 +0,0 @@ -fileFormatVersion: 2 -guid: b4c313afc05edee419fcacb1d46226b2 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Android: Android - second: - enabled: 1 - settings: {} - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/CHANGELOG.md b/CHANGELOG.md index c766b0ed..735ae6ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Backtrace Unity Release Notes +## Version 3.3.1 +- Improved Out-of-memory detection on iOS - Backtrace will report Out-of-memory exceptions when a memory warning occured and the application unexpectly closed. The Out-of-memory watcher will analyse game version, system version, debugger information and even more to determine if application closed by Out-of-memory exception or not. +- Backtrace will no longer send low memory warnings reports from Android or iOS. Instead, Backtrace will utilize iOS OOM detection and extend the embedded native report attributes on Android. +- Users can now enable or disable out-of-memory detection any time via UI/Backtrace API. + ## Version 3.3.0 - `BacktraceReport` stack trace now includes the file name of the stack frame. - Performance improvements: diff --git a/Editor/BacktraceClientConfigurationEditor.cs b/Editor/BacktraceClientConfigurationEditor.cs index ec1803aa..7581fbb8 100644 --- a/Editor/BacktraceClientConfigurationEditor.cs +++ b/Editor/BacktraceClientConfigurationEditor.cs @@ -28,6 +28,7 @@ public override void OnInspectorGUI() #endif #if UNITY_ANDROID || UNITY_IOS settings.HandleANR = EditorGUILayout.Toggle(BacktraceConfigurationLabels.LABEL_HANDLE_ANR, settings.HandleANR); + settings.OomReports = EditorGUILayout.Toggle(BacktraceConfigurationLabels.LABEL_HANDLE_OOM, settings.OomReports); #endif settings.GameObjectDepth = EditorGUILayout.IntField(BacktraceConfigurationLabels.LABEL_GAME_OBJECT_DEPTH, settings.GameObjectDepth); } diff --git a/Editor/BacktraceConfigurationEditor.cs b/Editor/BacktraceConfigurationEditor.cs index fb81e73a..0bdead12 100644 --- a/Editor/BacktraceConfigurationEditor.cs +++ b/Editor/BacktraceConfigurationEditor.cs @@ -46,6 +46,10 @@ public override void OnInspectorGUI() serializedObject.FindProperty("HandleANR"), new GUIContent(BacktraceConfigurationLabels.LABEL_HANDLE_ANR)); + EditorGUILayout.PropertyField( + serializedObject.FindProperty("OomReports"), + new GUIContent(BacktraceConfigurationLabels.LABEL_HANDLE_OOM)); + #if UNITY_2019_2_OR_NEWER && UNITY_ANDROID EditorGUILayout.PropertyField( serializedObject.FindProperty("SymbolsUploadToken"), diff --git a/Editor/BacktraceConfigurationLabels.cs b/Editor/BacktraceConfigurationLabels.cs index 972b80ca..666930de 100644 --- a/Editor/BacktraceConfigurationLabels.cs +++ b/Editor/BacktraceConfigurationLabels.cs @@ -10,12 +10,20 @@ internal static class BacktraceConfigurationLabels internal static string LABEL_DESTROY_CLIENT_ON_SCENE_LOAD = "Destroy client on new scene load (false - Backtrace managed)"; internal static string LABEL_SAMPLING = "Log random sampling rate"; internal static string LABEL_HANDLE_ANR = "Handle ANR (Application not responding)"; +#if UNITY_ANDROID || UNITY_IOS + internal static string LABEL_HANDLE_OOM = +#if UNITY_ANDROID + "(Early access) Send Low memory warnings to Backtrace"; +#elif UNITY_IOS + "(Early access) Send Out of memory exceptions to Backtrace"; +#endif +#endif internal static string CAPTURE_NATIVE_CRASHES = "Capture native crashes"; internal static string LABEL_REPORT_FILTER = "Filter reports"; internal static string LABEL_NUMBER_OF_LOGS = "Collect last n game logs"; internal static string LABEL_GAME_OBJECT_DEPTH = "Game object depth limit"; internal static string LABEL_IGNORE_SSL_VALIDATION = "Ignore SSL validation"; - internal static string LABEL_SEND_UNHANDLED_GAME_CRASHES_ON_STARTUP= "Send unhandled native game crashes on startup"; + internal static string LABEL_SEND_UNHANDLED_GAME_CRASHES_ON_STARTUP = "Send unhandled native game crashes on startup"; internal static string LABEL_USE_NORMALIZED_EXCEPTION_MESSAGE = "Use normalized exception message"; internal static string LABEL_PERFORMANCE_STATISTICS = "Enable performance statistics"; internal static string LABEL_SYMBOLS_UPLOAD_TOKEN = "Symbols upload token"; @@ -35,6 +43,5 @@ internal static class BacktraceConfigurationLabels internal static string LABEL_RETRY_LIMIT = "Maximum retries"; internal static string LABEL_RETRY_ORDER = "Retry order (FIFO/LIFO)"; - } } diff --git a/README.md b/README.md index 312b7e76..e34b21de 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - [Prerequisites](#prerequisites) - [Platforms Supported](#platforms-supported) - [Setup](#installation) -- [Best Practices](#plugin-best-practices) +- [Plugin Best Practices](#plugin-best-practices) - [Android Specific information](#android-specific-information) - [iOS Specific information](#ios-specific-information) - [Data Privacy](#data-privacy) @@ -27,10 +27,12 @@ //Read from manager BacktraceClient instance var backtraceClient = GameObject.Find("_Manager").GetComponent(); -try{ +try +{ //throw exception here } -catch(Exception exception){ +catch(Exception exception) +{ var report = new BacktraceReport(exception); backtraceClient.Send(report); } @@ -70,8 +72,8 @@ Web - WebGL Game Consoles - PlayStation4, Xbox One, Nintendo Switch There are some differences in capabilities that backtrace-unity provides based on the platform. Major capabilities are summarized as follows: * All Platforms - Errors, Unhandled Exceptions, Handled Exceptions, Custom Indexable Metadata, File Attachments*, Last N Log Lines, Automatic attachment of Screenshots, Client Side Deduplication Rules*, Client Side Submission Filtering, Client Side Submission Limits, Performance Diagnostics, Offline Database*(Except Nintendo Switch) -* Android -Identified by attribute `uname.sysname` = Android; ANRs (Hangs), Native Process and Memory Information, Java Exception Handler (Plugins, Exported Game in Android Studio), NDK crashes. -* iOS - Identified by attribute `uname.sysname` = IOS; ANRs (Hangs), Native Engine and Plugin Crashes. +* Android -Identified by attribute `uname.sysname` = Android; ANRs (Hangs), Native Process and Memory Information, Java Exception Handler (Plugins, Exported Game in Android Studio), NDK crashes, low memory warnings. +* iOS - Identified by attribute `uname.sysname` = IOS; ANRs (Hangs), Native Engine, Memory and Plugin Crashes. * WebGL - Identified by attribute `uname.sysname` = WebGL. The attribute device.model is currently used to share the browser information. Note that stacktraces for WebGL errors are only available if you choose to enable them in the Publishing Settings / Enable Exceptions drop down. More details [here](https://docs.unity3d.com/Manual/webgl-building.html) * Switch - Identified by attribute `uname.sysname` = Switch. Note that the attribute GUID is regenerated with each Switch restart (It is not an accurate count of number of Users or Devices. It is a count of Switch Sessions). Note that the current release does no support Offline Database or related features. * PlayStation4 - Identified by attribute `uname.sysname` = PS4 @@ -135,12 +137,13 @@ If you need to use more advanced configuration, `Initialize` method accepts a `B # Plugin best practices -The plugin will report on 5 'classes' or errors: +The plugin will report on 6 'classes' or errors: 1) Log Errors - Programmers use [Debug.LogError](https://docs.unity3d.com/ScriptReference/Debug.LogError.html), a variant of Debug.Log, to log error messages to the console. 2) Unhandled Exceptions - Unhandled Exceptions are exceptions in a game that occur outside of an explicit try / catch statement. 3) Handled Exceptions - Exceptions that are explicitly caught and handled. 4) Crashes - An end to the game play experience. The game crashes or restarts. -5) Hangs - A game is non responsive. Some platforms will tell the user "This app has stopped responding" +5) Hangs - A game is non responsive. Some platforms will tell the user “This app has stopped responding +6) Low memory warning - A game is receiving signals from the OS that memory is under pressure or crashed under memory pressure. The plugin provides 3 controls for managing what the client will report. - [SkipReports](#filtering-a-report) allows you to tell the client to only report on specific classes of these errors. @@ -191,10 +194,11 @@ The backtrace-unity library includes support for capturing Android NDK crashes a ## ANRs and Hangs -When configuring the backtrace-unity client for an Android deployment, programmers will have a toggle available in backtrace-unity GUI in the Unity Editor to enable or disable ANR or Hang reports. This will use the default of 5 seconds. The `error.type` for these reports will be `Hang`. +When configuring the backtrace-unity client for an Android deployment, programmers will have a toggle available in the backtrace-unity GUI in the Unity Editor to enable or disable ANR or Hang reports. This will use the default of 5 seconds. The `error.type` for these reports will be `Hang`. + +## Low Memory Reports (Early Access) -## Low Memory Reports -Backtrace can detect low memory situations for a game running in Unity on Android devices, and attempt to generate an error report with an associated dump object for further investigation. The `error.type` for these reports wiill be `Low Memory`. +Backtrace can detect low memory situations for a game running in Unity on Android devices, and attempt to generate an error report with an associated dump object for further investigation. When configuring the backtrace-unity client for an Android deployment, programmers will have a toggle available in the backtrace-unity GUI in the Unity Editor to enable or disable sending Low memory warnings to Backtrace. The `error.type` for these reports wiill be `Low Memory`. ## Symbols upload @@ -219,13 +223,13 @@ The backtrace-unity library includes support for capturing native iOS crashes as system and vm usage related information including system.memory.free, system.memory.used, system.memory.total, system.memory.active, system.memory.inactive, system.memory.wired are avaialble. ## Hangs -When configuring the backtrace-unity client for an iOS deployment, programmers will have a toggle available in backtrace-unity GUI in the Unity Editor to enable or disable ANR or Hang reports. This will use the default of 5 seconds. The `error.type` for these reports will be `Hang`. +When configuring the backtrace-unity client for an iOS deployment, programmers will have a toggle available in the backtrace-unity GUI in the Unity Editor to enable or disable ANR or Hang reports. This will use the default of 5 seconds. The `error.type` for these reports will be `Hang`. -## Low Memory Reports -Backtrace can detect low memory situations for a game running in Unity on iOS devices, and attempt to generate an error report with an associated dump object for further investigation. The `error.type` for these reports wiill be `Low Memory`. +## Low Memory Reports (Early access) +Backtrace can detect low memory situations for a game running in Unity on iOS devices, and attempt to generate an error report with an associated dump object for further investigation. When configuring the backtrace-unity client for an iOS deployment, programmers will have a toggle available in the backtrace-unity GUI in the Unity Editor to enable or disable sending Out of memory exceptions to Backtrace. The `error.type` for these reports wiill be `Low Memory`. ## Native Crashes -When configuring the backtrace-unity client for an iOS deployment in the Unity Editor, programmers will have a toggle to enable or disable `Capture native crashes`. If this is enabled, the backtrace-unity client will ensure the crash report is generated, stored locally, and uploaded upon next game start. Unity crash reporter might prevent Backtrace Crash reporte from sending crashes to Backtrace. To be sure Backtrace is able to collect and send data please set "Enable CrashReport API" to false. +When configuring the backtrace-unity client for an iOS deployment in the Unity Editor, programmers will have a toggle to enable or disable `Capture native crashes`. If this is enabled, the backtrace-unity client will ensure the crash report is generated, stored locally, and uploaded upon next game start. Unity crash reporter might prevent Backtrace Crash reporter from sending crashes to Backtrace. To be sure Backtrace is able to collect and send data please set "Enable CrashReport API" to false. ![Enable symbols](./Documentation~/images/Disable-ios-unity-crash-reporter.png) ## Debug Symbol upload diff --git a/Runtime/BacktraceClient.cs b/Runtime/BacktraceClient.cs index 4500aa97..d5615505 100644 --- a/Runtime/BacktraceClient.cs +++ b/Runtime/BacktraceClient.cs @@ -20,7 +20,7 @@ public class BacktraceClient : MonoBehaviour, IBacktraceClient { public BacktraceConfiguration Configuration; - public const string VERSION = "3.3.0"; + public const string VERSION = "3.3.1"; public bool Enabled { get; private set; } /// @@ -400,6 +400,11 @@ public void Refresh() } } + private void OnApplicationQuit() + { + _nativeClient?.Disable(); + } + private void Awake() { Refresh(); @@ -709,16 +714,14 @@ internal void HandleLowMemory() Debug.LogWarning("Please enable BacktraceClient first."); return; } - const string lowMemoryMessage = "OOMException: Out of memory detected."; - _backtraceLogManager.Enqueue(new BacktraceUnityMessage(lowMemoryMessage, string.Empty, LogType.Error)); - - // try to send report about OOM from managed layer if native layer is disabled. - bool nativeSendResult = _nativeClient != null ? _nativeClient.OnOOM() : false; - if (!nativeSendResult) + if (Configuration.OomReports && _nativeClient != null) { - var oom = new BacktraceUnhandledException(lowMemoryMessage, string.Empty); - SendUnhandledException(oom); + // inform native layer about oom error + _nativeClient.OnOOM(); + } + const string lowMemoryMessage = "OOMException: Out of memory detected."; + _backtraceLogManager.Enqueue(new BacktraceUnityMessage(lowMemoryMessage, string.Empty, LogType.Error)); } #endif diff --git a/Runtime/Model/BacktraceClientConfiguration.cs b/Runtime/Model/BacktraceClientConfiguration.cs index 5275bb3d..0e99ce48 100644 --- a/Runtime/Model/BacktraceClientConfiguration.cs +++ b/Runtime/Model/BacktraceClientConfiguration.cs @@ -13,6 +13,7 @@ public class BacktraceClientConfiguration : ScriptableObject public bool IgnoreSslValidation = false; public bool DestroyOnLoad = true; public bool HandleANR = true; + public bool OomReports = false; public int GameObjectDepth = 0; public MiniDumpType MinidumpType = MiniDumpType.None; diff --git a/Runtime/Model/BacktraceConfiguration.cs b/Runtime/Model/BacktraceConfiguration.cs index 1e076c4f..99867a79 100644 --- a/Runtime/Model/BacktraceConfiguration.cs +++ b/Runtime/Model/BacktraceConfiguration.cs @@ -104,15 +104,25 @@ public class BacktraceConfiguration : ScriptableObject #endif public bool CaptureNativeCrashes = true; -#endif - -#if UNITY_ANDROID || UNITY_IOS /// /// Handle ANR events - Application not responding /// [Tooltip("Handle ANR events - Application not responding")] public bool HandleANR = true; +#if UNITY_ANDROID + /// + /// Send Low memory warnings to Backtrace + /// + [Tooltip("(Early access) Send Low memory warnings to Backtrace")] +#elif UNITY_IOS + /// + /// Send Out of memory exceptions to Backtrace. + /// + [Tooltip("(Early access) Send Out of memory exceptions to Backtrace")] +#endif + public bool OomReports = false; + #if UNITY_2019_2_OR_NEWER /// /// Symbols upload token diff --git a/Runtime/Model/JsonData/BacktraceAttributes.cs b/Runtime/Model/JsonData/BacktraceAttributes.cs index 9f096ea0..02f77113 100644 --- a/Runtime/Model/JsonData/BacktraceAttributes.cs +++ b/Runtime/Model/JsonData/BacktraceAttributes.cs @@ -67,6 +67,7 @@ public BacktraceAttributes(BacktraceReport report, Dictionary cl SetProcessAttributes(onlyBuiltInAttributes); SetSceneInformation(onlyBuiltInAttributes); } + private BacktraceAttributes() { } public BacktraceJObject ToJson() diff --git a/Runtime/Native/Android/NativeClient.cs b/Runtime/Native/Android/NativeClient.cs index f2984f19..b5413289 100644 --- a/Runtime/Native/Android/NativeClient.cs +++ b/Runtime/Native/Android/NativeClient.cs @@ -29,12 +29,57 @@ internal class NativeClient : INativeClient [DllImport("backtrace-native", EntryPoint = "DumpWithoutCrash")] private static extern bool NativeReport(IntPtr message); + /// + /// Native client built-in specific attributes + /// + private readonly Dictionary _builtInAttributes = new Dictionary(); + /// + /// Attribute maps - list of attribute maps that allows Backtrace-Unity to rename attributes + /// grabbed from android specific directories + /// + private readonly Dictionary _attributeMapping = new Dictionary(); + + private void SetDefaultAttributeMaps() + { + _attributeMapping.Add("FDSize", "descriptor.count"); + _attributeMapping.Add("VmPeak", "vm.vma.peak"); + _attributeMapping.Add("VmSize", "vm.vma.size"); + _attributeMapping.Add("VmLck", "vm.locked.size"); + _attributeMapping.Add("VmHWM", "vm.rss.peak"); + _attributeMapping.Add("VmRSS", "vm.rss.size"); + _attributeMapping.Add("VmStk", "vm.stack.size"); + _attributeMapping.Add("VmData", "vm.data"); + _attributeMapping.Add("VmExe", "vm.exe"); + _attributeMapping.Add("VmLib", "vm.shared.size"); + _attributeMapping.Add("VmPTE", "vm.pte.size"); + _attributeMapping.Add("VmSwap", "vm.swap.size"); + _attributeMapping.Add("State", "state"); + _attributeMapping.Add("voluntary_ctxt_switches", "sched.cs.voluntary"); + _attributeMapping.Add("nonvoluntary_ctxt_switches", "sched.cs.involuntary"); + _attributeMapping.Add("SigPnd", "vm.sigpnd"); + _attributeMapping.Add("ShdPnd", "vm.shdpnd"); + _attributeMapping.Add("Threads", "vm.threads"); + _attributeMapping.Add("MemTotal", "system.memory.total"); + _attributeMapping.Add("MemFree", "system.memory.free"); + _attributeMapping.Add("Buffers", "system.memory.buffers"); + _attributeMapping.Add("Cached", "system.memory.cached"); + _attributeMapping.Add("SwapCached", "system.memory.swap.cached"); + _attributeMapping.Add("Active", "system.memory.active"); + _attributeMapping.Add("Inactive", "system.memory.inactive"); + _attributeMapping.Add("SwapTotal", "system.memory.swap.total"); + _attributeMapping.Add("SwapFree", "system.memory.swap.free"); + _attributeMapping.Add("Dirty", "system.memory.dirty"); + _attributeMapping.Add("Writeback", "system.memory.writeback"); + _attributeMapping.Add("Slab", "system.memory.slab"); + _attributeMapping.Add("VmallocTotal", "system.memory.vmalloc.total"); + _attributeMapping.Add("VmallocUsed", "system.memory.vmalloc.used"); + _attributeMapping.Add("VmallocChunk", "system.memory.vmalloc.chunk"); + } private readonly BacktraceConfiguration _configuration; // Android native interface paths private const string _namespace = "backtrace.io.backtrace_unity_android_plugin"; - private readonly string _nativeAttributesPath = string.Format("{0}.{1}", _namespace, "BacktraceAttributes"); private readonly string _anrPath = string.Format("{0}.{1}", _namespace, "BacktraceANRWatchdog"); /// @@ -57,17 +102,40 @@ internal class NativeClient : INativeClient public NativeClient(string gameObjectName, BacktraceConfiguration configuration) { _configuration = configuration; + SetDefaultAttributeMaps(); if (!_enabled) { return; } + #if UNITY_ANDROID _handlerANR = _configuration.HandleANR; HandleNativeCrashes(); HandleAnr(gameObjectName, "OnAnrDetected"); + + // read device manufacturer + using (var build = new AndroidJavaClass("android.os.Build")) + { + _builtInAttributes["device.manufacturer"] = build.GetStatic("MANUFACTURER").ToString(); + } #endif + } + /// + /// Get path to the native libraries directory + /// + /// Path to the native libraries directory + private string GetNativeDirectoryPath() + { + using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + using (var activity = unityPlayer.GetStatic("currentActivity")) + using (var context = activity.Call("getApplicationContext")) + using (var applicationInfo = context.Call("getApplicationInfo")) + { + return applicationInfo.Get("nativeLibraryDir"); + } } + /// /// Start crashpad process to handle native Android crashes /// @@ -103,18 +171,22 @@ private void HandleNativeCrashes() using (var version = new AndroidJavaClass("android.os.Build$VERSION")) { int apiLevel = version.GetStatic("SDK_INT"); + _builtInAttributes["device.sdk"] = apiLevel.ToString(); if (apiLevel < 21) { Debug.LogWarning("Backtrace native integration status: Unsupported Android API level"); return; } } - var libDirectory = Path.Combine(Path.GetDirectoryName(Application.dataPath), "lib"); + + var libDirectory = GetNativeDirectoryPath(); if (!Directory.Exists(libDirectory)) { return; } - var crashpadHandlerPath = Directory.GetFiles(libDirectory, "libcrashpad_handler.so", SearchOption.AllDirectories).FirstOrDefault(); + const string crashpadHandlerName = "libcrashpad_handler.so"; + var crashpadHandlerPath = Path.Combine(libDirectory, crashpadHandlerName); + if (string.IsNullOrEmpty(crashpadHandlerPath)) { Debug.LogWarning("Backtrace native integration status: Cannot find crashpad library"); @@ -161,22 +233,39 @@ public void GetAttributes(Dictionary result) { return; } + // rewrite built in attributes to report attributes + foreach (var builtInAttribute in _builtInAttributes) + { + result.Add(builtInAttribute.Key, builtInAttribute.Value); + } - using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) - using (var activity = unityPlayer.GetStatic("currentActivity")) - using (var context = activity.Call("getApplicationContext")) - using (var backtraceAttributes = new AndroidJavaObject(_nativeAttributesPath)) + var processId = System.Diagnostics.Process.GetCurrentProcess().Id; + var filesToRead = new string[2] { $"/proc/{processId}/status", "/proc/meminfo" }; + foreach (var diagnosticFilePath in filesToRead) { - var androidAttributes = backtraceAttributes.Call("GetAttributes", new object[] { context }); - var entrySet = androidAttributes.Call("entrySet"); - var iterator = entrySet.Call("iterator"); - while (iterator.Call("hasNext")) + if (!File.Exists(diagnosticFilePath)) { - var pair = iterator.Call("next"); - - var key = pair.Call("getKey"); - var value = pair.Call("getValue"); - result[key] = value; + continue; + } + foreach (var line in File.ReadLines(diagnosticFilePath)) + { + string[] entries = line.Split(':'); + if (entries.Length != 2) + { + continue; + } + var key = entries[0].Trim(); + if (!_attributeMapping.ContainsKey(key)) + { + continue; + } + key = _attributeMapping[key]; + var value = entries[1]; + if (value.EndsWith("kB")) + { + value = value.Substring(0, value.LastIndexOf("k")).Trim(); + } + result.Add(key, value); } } } @@ -226,7 +315,7 @@ public void HandleAnr(string gameObjectName, string callbackName) reported = true; if (AndroidJNI.AttachCurrentThread() == 0) { - // set temporary attribute to "Hang" + // set temporary attribute to "Hang" AddAttribute( AndroidJNI.NewStringUTF("error.type"), AndroidJNI.NewStringUTF("Hang")); @@ -279,17 +368,8 @@ public void SetAttribute(string key, string value) /// true - if native crash reprorter is enabled. Otherwise false. public bool OnOOM() { - if (!_enabled || _captureNativeCrashes) - { - return false; - } - - // set temporary attribute to "Hang" - SetAttribute("error.type", "Low Memory"); - NativeReport(AndroidJNI.NewStringUTF("OOMException: Out of memory detected.")); - // update error.type attribute in case when crash happen - SetAttribute("error.type", "Crash"); - + SetAttribute("memory.warning", "true"); + SetAttribute("memory.warning.date", DateTime.Now.ToString()); return true; } diff --git a/Runtime/Native/iOS/NativeClient.cs b/Runtime/Native/iOS/NativeClient.cs index 8938942f..505f35d7 100644 --- a/Runtime/Native/iOS/NativeClient.cs +++ b/Runtime/Native/iOS/NativeClient.cs @@ -28,19 +28,19 @@ internal struct Entry } [DllImport("__Internal", EntryPoint = "StartBacktraceIntegration")] - private static extern void Start(string plCrashReporterUrl, string[] attributeKeys, string[] attributeValues, int size); + private static extern void Start(string plCrashReporterUrl, string[] attributeKeys, string[] attributeValues, int size, bool enableOomSupport); [DllImport("__Internal", EntryPoint = "NativeReport")] - public static extern void NativeReport(string message); + private static extern void NativeReport(string message); [DllImport("__Internal", EntryPoint = "Crash")] - public static extern string Crash(); + private static extern string Crash(); - [DllImport("__Internal", EntryPoint = "GetAttibutes")] - public static extern void GetNativeAttibutes(out IntPtr attributes, out int keysCount); + [DllImport("__Internal", EntryPoint = "GetAttributes")] + private static extern void GetNativeAttributes(out IntPtr attributes, out int keysCount); [DllImport("__Internal", EntryPoint = "AddAttribute")] - public static extern void AddAttribute(string key, string value); + private static extern void AddAttribute(string key, string value); private static bool INITIALIZED = false; @@ -97,7 +97,7 @@ private void HandleNativeCrashes(BacktraceConfiguration configuration) var attributeKeys = backtraceAttributes.Attributes.Keys.ToArray(); var attributeValues = backtraceAttributes.Attributes.Values.ToArray(); - Start(plcrashreporterUrl.ToString(), attributeKeys, attributeValues, attributeValues.Length); + Start(plcrashreporterUrl.ToString(), attributeKeys, attributeValues, attributeValues.Length, configuration.OomReports); } /// @@ -110,7 +110,7 @@ public void GetAttributes(Dictionary result) { return; } - GetNativeAttibutes(out IntPtr pUnmanagedArray, out int keysCount); + GetNativeAttributes(out IntPtr pUnmanagedArray, out int keysCount); for (int i = 0; i < keysCount; i++) { @@ -153,7 +153,7 @@ public void HandleAnr(string gameObjectName, string callbackName) SetAttribute("error.type", "Hang"); NativeReport("ANRException: Blocked thread detected."); // update error.type attribute in case when crash happen - SetAttribute("error.type", "Crash"); + SetAttribute("error.type", "Crash"); reported = true; } } @@ -205,12 +205,10 @@ public bool OnOOM() { return false; } - // set temporary attribute to "Hang" - SetAttribute("error.type", "Low Memory"); - NativeReport("OOMException: Out of memory detected."); - // update error.type attribute in case when crash happen - SetAttribute("error.type", "Crash"); - + // oom support will be handled by native plugin - this will prevent + // false positive reports + // to avoid reporting low memory warning when application didn't crash + // native plugin will analyse previous application session return true; } diff --git a/iOS/libBacktrace-Unity-Cocoa.a b/iOS/libBacktrace-Unity-Cocoa.a index 9220e9c4..511a5b02 100644 Binary files a/iOS/libBacktrace-Unity-Cocoa.a and b/iOS/libBacktrace-Unity-Cocoa.a differ diff --git a/package.json b/package.json index 1c5dc397..708210b6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "io.backtrace.unity", "displayName": "Backtrace", - "version": "3.3.0", + "version": "3.3.1", "unity": "2017.1", "description": "Backtrace's integration with Unity games allows customers to capture and report handled and unhandled Unity exceptions to their Backtrace instance, instantly offering the ability to prioritize and debug software errors.", "keywords": [