Skip to content

Commit

Permalink
feat(memoryTrimLevel): gather the app.memoryTrimLevel metadata and …
Browse files Browse the repository at this point in the history
…include it in reports
  • Loading branch information
lemnik committed Aug 17, 2021
1 parent e2f08e5 commit 0806942
Show file tree
Hide file tree
Showing 20 changed files with 130 additions and 44 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

* The `app.lowMemory` value always report the most recent `onTrimMemory`/`onLowMemory` status
[#1342](https://github.com/bugsnag/bugsnag-android/pull/1342)

* Added the `app.memoryTrimLevel` metadata to report a description of the latest `onTrimMemory` status
[#1344](https://github.com/bugsnag/bugsnag-android/pull/1344)

## 5.11.0 (2021-08-05)

Expand Down
5 changes: 2 additions & 3 deletions bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<CurrentIssues>
<ID>ImplicitDefaultLocale:DeliveryHeaders.kt$String.format("%02x", byte)</ID>
<ID>LongParameterList:App.kt$App$( /** * The architecture of the running application binary */ var binaryArch: String?, /** * The package name of the application */ var id: String?, /** * The release stage set in [Configuration.releaseStage] */ var releaseStage: String?, /** * The version of the application set in [Configuration.version] */ var version: String?, /** The revision ID from the manifest (React Native apps only) */ var codeBundleId: String?, /** * The unique identifier for the build of the application set in [Configuration.buildUuid] */ var buildUuid: String?, /** * The application type set in [Configuration#version] */ var type: String?, /** * The version code of the application set in [Configuration.versionCode] */ var versionCode: Number? )</ID>
<ID>LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val logger: Logger )</ID>
<ID>LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )</ID>
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String? )</ID>
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String?, memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array&lt;String&gt;?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ var runtimeVersions: MutableMap&lt;String, Any&gt;? )</ID>
<ID>LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array&lt;String&gt;? )</ID>
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger )</ID>
Expand All @@ -27,7 +27,6 @@
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityInternal(severity: Severity)</ID>
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityReason(@SeverityReason.SeverityReasonType reason: String)</ID>
<ID>ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, streamable: JsonStream.Streamable, headers: Map&lt;String, String?&gt; ): DeliveryStatus</ID>
<ID>SwallowedException:AppDataCollector.kt$AppDataCollector$catch (exception: Exception) { logger.w("Could not check lowMemory status") }</ID>
<ID>SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null }</ID>
<ID>SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class AppDataCollectorTest {
client.immutableConfig,
client.sessionTracker,
am,
client.launchCrashTracker
client.launchCrashTracker,
client.memoryTrimState
)
val app = collector.getAppDataMetadata()
assertNull(app["backgroundWorkRestricted"])
Expand All @@ -66,7 +67,8 @@ class AppDataCollectorTest {
client.immutableConfig,
client.sessionTracker,
am,
client.launchCrashTracker
client.launchCrashTracker,
client.memoryTrimState
)
client.context = "Some Custom Context"
client.sessionTracker.updateForegroundTracker("MyActivity", true, 0L)
Expand All @@ -88,7 +90,8 @@ class AppDataCollectorTest {
client.immutableConfig,
client.sessionTracker,
am,
client.launchCrashTracker
client.launchCrashTracker,
client.memoryTrimState
)
val app = collector.getAppDataMetadata()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,13 @@ public void testAppDataCollection() {
public void testAppDataMetadata() {
client = generateClient();
Map<String, Object> app = client.getAppDataCollector().getAppDataMetadata();
assertEquals(5, app.size());
assertEquals(6, app.size());
assertEquals("Bugsnag Android Tests", app.get("name"));
assertEquals("com.bugsnag.android.core.test", app.get("processName"));
assertNotNull(app.get("memoryUsage"));
assertTrue(app.containsKey("activeScreen"));
assertNotNull(app.get("lowMemory"));
assertNotNull(app.get("memoryTrimLevel"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ internal class AppDataCollector(
private val config: ImmutableConfig,
private val sessionTracker: SessionTracker,
private val activityManager: ActivityManager?,
private val launchCrashTracker: LaunchCrashTracker
private val launchCrashTracker: LaunchCrashTracker,
private val memoryTrimState: MemoryTrimState
) {
/**
* Is the app considered to be an a low-memory state as defined by
* [android.content.ComponentCallbacks]
*/
var isLowMemory: Boolean = false

var codeBundleId: String? = null

private val packageName: String = appContext.packageName
Expand Down Expand Up @@ -56,7 +53,8 @@ internal class AppDataCollector(
map["name"] = appName
map["activeScreen"] = sessionTracker.contextActivity
map["memoryUsage"] = getMemoryUsage()
map["lowMemory"] = isLowMemory
map["lowMemory"] = memoryTrimState.isLowMemory
map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription

bgWorkRestricted?.let {
map["backgroundWorkRestricted"] = bgWorkRestricted
Expand Down
17 changes: 12 additions & 5 deletions bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@NonNull
final BreadcrumbState breadcrumbState;

@NonNull
final MemoryTrimState memoryTrimState = new MemoryTrimState();

@NonNull
protected final EventStore eventStore;

Expand Down Expand Up @@ -162,7 +165,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {

DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule,
configModule, systemServiceModule, trackerModule,
bgTaskService, connectivity, storageModule.getDeviceId());
bgTaskService, connectivity, storageModule.getDeviceId(), memoryTrimState);
dataCollectionModule.resolveDependencies(bgTaskService, TaskType.IO);
appDataCollector = dataCollectionModule.getAppDataCollector();
deviceDataCollector = dataCollectionModule.getDeviceDataCollector();
Expand Down Expand Up @@ -337,11 +340,12 @@ public Unit invoke(String oldOrientation, String newOrientation) {
clientObservable.postOrientationChange(newOrientation);
return null;
}
}, new Function1<Boolean, Unit>() {
}, new Function2<Boolean, Integer, Unit>() {
@Override
public Unit invoke(Boolean isLowMemory) {
appDataCollector.setLowMemory(Boolean.TRUE.equals(isLowMemory));
clientObservable.postMemoryTrimEvent(isLowMemory);
public Unit invoke(Boolean isLowMemory, Integer memoryTrimLevel) {
memoryTrimState.setLowMemory(Boolean.TRUE.equals(isLowMemory));
memoryTrimState.setMemoryTrimLevel(memoryTrimLevel);
memoryTrimState.emitObservableEvent();
return null;
}
}
Expand Down Expand Up @@ -384,6 +388,7 @@ void addObserver(StateObserver observer) {
contextState.addObserver(observer);
deliveryDelegate.addObserver(observer);
launchCrashTracker.addObserver(observer);
memoryTrimState.addObserver(observer);
}

void removeObserver(StateObserver observer) {
Expand All @@ -395,6 +400,7 @@ void removeObserver(StateObserver observer) {
contextState.removeObserver(observer);
deliveryDelegate.removeObserver(observer);
launchCrashTracker.removeObserver(observer);
memoryTrimState.removeObserver(observer);
}

/**
Expand All @@ -404,6 +410,7 @@ void syncInitialState() {
metadataState.emitObservableEvent();
contextState.emitObservableEvent();
userState.emitObservableEvent();
memoryTrimState.emitObservableEvent();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.content.res.Configuration
internal class ClientComponentCallbacks(
private val deviceDataCollector: DeviceDataCollector,
private val cb: (oldOrientation: String?, newOrientation: String?) -> Unit,
val callback: (Boolean) -> Unit
val memoryCallback: (Boolean, Int?) -> Unit
) : ComponentCallbacks2 {

override fun onConfigurationChanged(newConfig: Configuration) {
Expand All @@ -19,10 +19,10 @@ internal class ClientComponentCallbacks(
}

override fun onTrimMemory(level: Int) {
callback(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
memoryCallback(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE, level)
}

override fun onLowMemory() {
callback(true)
memoryCallback(true, null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ internal class ClientObservable : BaseObservable() {
updateState { StateEvent.UpdateOrientation(orientation) }
}

fun postMemoryTrimEvent(isLowMemory: Boolean) {
updateState { StateEvent.UpdateMemoryTrimEvent(isLowMemory) }
}

fun postNdkInstall(
conf: ImmutableConfig,
lastRunInfoPath: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ internal class DataCollectionModule(
trackerModule: TrackerModule,
bgTaskService: BackgroundTaskService,
connectivity: Connectivity,
deviceId: String?
deviceId: String?,
memoryTrimState: MemoryTrimState
) : DependencyModule() {

private val ctx = contextModule.ctx
Expand All @@ -33,7 +34,8 @@ internal class DataCollectionModule(
cfg,
trackerModule.sessionTracker,
systemServiceModule.activityManager,
trackerModule.launchCrashTracker
trackerModule.launchCrashTracker,
memoryTrimState
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.bugsnag.android

import android.content.ComponentCallbacks2

internal class MemoryTrimState : BaseObservable() {
var isLowMemory: Boolean = false
var memoryTrimLevel: Int? = null

val trimLevelDescription: String get() = descriptionFor(memoryTrimLevel)

fun emitObservableEvent() {
updateState {
StateEvent.UpdateMemoryTrimEvent(
isLowMemory,
memoryTrimLevel,
trimLevelDescription
)
}
}

private fun descriptionFor(memoryTrimLevel: Int?) = when (memoryTrimLevel) {
null -> "None"
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> "Complete"
ComponentCallbacks2.TRIM_MEMORY_MODERATE -> "Moderate"
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> "Background"
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> "UI hidden"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> "Running critical"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> "Running low"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> "Running moderate"
else -> "Unknown ($memoryTrimLevel)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,9 @@ sealed class StateEvent { // JvmField allows direct field access optimizations

class UpdateUser(@JvmField val user: User) : StateEvent()

class UpdateMemoryTrimEvent(@JvmField val isLowMemory: Boolean) : StateEvent()
class UpdateMemoryTrimEvent(
@JvmField val isLowMemory: Boolean,
@JvmField val memoryTrimLevel: Int? = null,
@JvmField val memoryTrimLevelDescription: String = "None"
) : StateEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class AppDataCollectorForegroundTest {
@Mock
internal lateinit var launchCrashTracker: LaunchCrashTracker

@Mock
internal lateinit var memoryTrimState: MemoryTrimState

private lateinit var appDataCollector: AppDataCollector

@Before
Expand All @@ -41,7 +44,8 @@ class AppDataCollectorForegroundTest {
BugsnagTestUtils.generateImmutableConfig(),
sessionTracker,
null,
launchCrashTracker
launchCrashTracker,
memoryTrimState
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal class AppDataCollectorSerializationTest {
val sessionTracker = mock(SessionTracker::class.java)
val launchCrashTracker = mock(LaunchCrashTracker::class.java)
val config = BugsnagTestUtils.generateConfiguration()
val memoryTrimState = MemoryTrimState()

// populate summary fields
config.appType = "React Native"
Expand All @@ -50,7 +51,8 @@ internal class AppDataCollectorSerializationTest {
convert(config),
sessionTracker,
am,
launchCrashTracker
launchCrashTracker,
memoryTrimState
)
appData.codeBundleId = "foo-99"
appData.setBinaryArch("x86")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal class AppMetadataSerializationTest {
val sessionTracker = mock(SessionTracker::class.java)
val launchCrashTracker = mock(LaunchCrashTracker::class.java)
val config = BugsnagTestUtils.generateConfiguration()
val memoryTrimState = MemoryTrimState()

// populate summary fields
config.appType = "React Native"
Expand All @@ -50,7 +51,8 @@ internal class AppMetadataSerializationTest {
convertToImmutableConfig(config, null, null, ApplicationInfo()),
sessionTracker,
am,
launchCrashTracker
launchCrashTracker,
memoryTrimState
)
appData.codeBundleId = "foo-99"
appData.setBinaryArch("x86")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bugsnag.android

import android.content.ComponentCallbacks2
import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand All @@ -11,6 +12,9 @@ import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class ClientComponentCallbacksMemoryCallbackTest {

@Mock
lateinit var context: Context

@Mock
internal lateinit var deviceDataCollector: DeviceDataCollector
private lateinit var clientComponentCallbacks: ClientComponentCallbacks
Expand All @@ -21,9 +25,8 @@ class ClientComponentCallbacksMemoryCallbackTest {
fun setUp() {
clientComponentCallbacks = ClientComponentCallbacks(
deviceDataCollector,
{ _: String?, _: String? -> },
this::isLowMemory::set
)
{ _: String?, _: String? -> }
) { lowMemory, _ -> isLowMemory = lowMemory }
isLowMemory = null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ internal class ConfigChangeReceiverTest {
@Mock
lateinit var bgTaskService: BackgroundTaskService

private val memoryCallback: (Boolean, Int?) -> Unit = { _, _ -> }

private fun createDeviceDataCollector(): DeviceDataCollector = DeviceDataCollector(
connectivity,
appContext,
Expand Down Expand Up @@ -92,7 +94,11 @@ internal class ConfigChangeReceiverTest {
assertEquals("portrait", new)
}
config.orientation = Configuration.ORIENTATION_UNDEFINED
val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {})
val callbacks = ClientComponentCallbacks(
createDeviceDataCollector(),
orientationCb,
memoryCallback
)

configUpdate.orientation = Configuration.ORIENTATION_PORTRAIT
callbacks.onConfigurationChanged(configUpdate)
Expand All @@ -106,7 +112,11 @@ internal class ConfigChangeReceiverTest {
assertEquals("landscape", new)
}
config.orientation = Configuration.ORIENTATION_PORTRAIT
val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {})
val callbacks = ClientComponentCallbacks(
createDeviceDataCollector(),
orientationCb,
memoryCallback
)

configUpdate.orientation = Configuration.ORIENTATION_LANDSCAPE
callbacks.onConfigurationChanged(configUpdate)
Expand All @@ -123,7 +133,11 @@ internal class ConfigChangeReceiverTest {
}
`when`(resources.configuration).thenReturn(config)
config.orientation = Configuration.ORIENTATION_PORTRAIT
val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {})
val callbacks = ClientComponentCallbacks(
createDeviceDataCollector(),
orientationCb,
memoryCallback
)

configUpdate.orientation = Configuration.ORIENTATION_PORTRAIT
callbacks.onConfigurationChanged(configUpdate)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"memoryTrimLevel": "None",
"activeScreen": "MyActivity",
"name": "MyApp",
"lowMemory": false
Expand Down
Loading

0 comments on commit 0806942

Please sign in to comment.