Skip to content

Commit

Permalink
perf(DependencyModule): removed future and replaced it with `resolv…
Browse files Browse the repository at this point in the history
…edValueOf` to avoid reflection during startup and reduce the number of objects that need to be allocated
  • Loading branch information
lemnik committed May 10, 2022
1 parent 2fe95c0 commit 3270ef5
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 69 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## TBD

### Enhancements

* Small performance improvements to `Bugnag.start`
[#1683](https://github.com/bugsnag/bugsnag-android/pull/1683)

## 5.22.2 (2022-05-04)

### Bug fixes
Expand Down
3 changes: 2 additions & 1 deletion bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<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?, memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, private val bgTaskService: BackgroundTaskService, private val connectivity: Connectivity, private val deviceId: String?, memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array&lt;String>?, /** * 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 */ runtimeVersions: MutableMap&lt;String, Any>? )</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>? )</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 @@ -34,6 +34,7 @@
<ID>SwallowedException:BugsnagEventMapper.kt$BugsnagEventMapper$catch (pe: IllegalArgumentException) { ndkDateFormatHolder.get()!!.parse(this) ?: throw IllegalArgumentException("cannot parse date $this") }</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:DependencyModule.kt$DependencyModule$catch (exception: Exception) { // ignore failures }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") }</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ internal class DataCollectionModule(
configModule: ConfigModule,
systemServiceModule: SystemServiceModule,
trackerModule: TrackerModule,
bgTaskService: BackgroundTaskService,
connectivity: Connectivity,
deviceId: String?,
private val bgTaskService: BackgroundTaskService,
private val connectivity: Connectivity,
private val deviceId: String?,
memoryTrimState: MemoryTrimState
) : DependencyModule() {

Expand All @@ -27,24 +27,25 @@ internal class DataCollectionModule(
private val deviceBuildInfo: DeviceBuildInfo = DeviceBuildInfo.defaultInfo()
private val dataDir = Environment.getDataDirectory()

val appDataCollector by future {
AppDataCollector(
ctx,
ctx.packageManager,
cfg,
trackerModule.sessionTracker,
systemServiceModule.activityManager,
trackerModule.launchCrashTracker,
memoryTrimState
)
}
val appDataCollector = AppDataCollector(
ctx,
ctx.packageManager,
cfg,
trackerModule.sessionTracker,
systemServiceModule.activityManager,
trackerModule.launchCrashTracker,
memoryTrimState
)

private val rootDetector by future {
RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
}
private lateinit var rootDetector: RootDetector
private lateinit var _deviceDataCollector: DeviceDataCollector

val deviceDataCollector: DeviceDataCollector
get() = resolvedValueOf { _deviceDataCollector }

val deviceDataCollector by future {
DeviceDataCollector(
override fun resolveDependencies() {
rootDetector = RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
_deviceDataCollector = DeviceDataCollector(
connectivity,
ctx,
ctx.resources,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,24 @@ internal class EventStorageModule(

private val cfg = configModule.config

private val delegate by future {
InternalReportDelegate(
contextModule.ctx,
cfg.logger,
cfg,
systemServiceModule.storageManager,
dataCollectionModule.appDataCollector,
dataCollectionModule.deviceDataCollector,
trackerModule.sessionTracker,
notifier,
bgTaskService
)
}
private val delegate = InternalReportDelegate(
contextModule.ctx,
cfg.logger,
cfg,
systemServiceModule.storageManager,
dataCollectionModule.appDataCollector,
dataCollectionModule.deviceDataCollector,
trackerModule.sessionTracker,
notifier,
bgTaskService
)

val eventStore by future { EventStore(cfg, cfg.logger, notifier, bgTaskService, delegate, callbackState) }
val eventStore = EventStore(
cfg,
cfg.logger,
notifier,
bgTaskService,
delegate,
callbackState
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,57 @@ import com.bugsnag.android.internal.dag.DependencyModule
* A dependency module which constructs the objects that store information to disk in Bugsnag.
*/
internal class StorageModule(
appContext: Context,
immutableConfig: ImmutableConfig,
logger: Logger
private val appContext: Context,
private val immutableConfig: ImmutableConfig,
private val logger: Logger
) : DependencyModule() {

val sharedPrefMigrator by future { SharedPrefMigrator(appContext) }
private lateinit var _sharedPrefMigrator: SharedPrefMigrator
private lateinit var _userStore: UserStore
private lateinit var _lastRunInfoStore: LastRunInfoStore
private lateinit var _sessionStore: SessionStore

private val deviceIdStore by future {
DeviceIdStore(
appContext,
sharedPrefMigrator = sharedPrefMigrator,
logger = logger
)
}
private var _deviceId: String? = null
private var _lastRunInfo: LastRunInfo? = null

val sharedPrefMigrator get() = resolvedValueOf { _sharedPrefMigrator }
val deviceId get() = resolvedValueOf { _deviceId }
val userStore get() = resolvedValueOf { _userStore }
val lastRunInfoStore get() = resolvedValueOf { _lastRunInfoStore }
val sessionStore get() = resolvedValueOf { _sessionStore }
val lastRunInfo get() = resolvedValueOf { _lastRunInfo }

val deviceId by future { deviceIdStore.loadDeviceId() }
override fun resolveDependencies() {
_sharedPrefMigrator = SharedPrefMigrator(appContext)
_deviceId = resolveDeviceId()

val userStore by future {
UserStore(
_userStore = UserStore(
immutableConfig,
deviceId,
sharedPrefMigrator = sharedPrefMigrator,
_deviceId,
sharedPrefMigrator = _sharedPrefMigrator,
logger = logger
)

_lastRunInfoStore = LastRunInfoStore(immutableConfig)
_sessionStore = SessionStore(immutableConfig, logger, null)

_lastRunInfo = resolveLastRunInfo()
}

val lastRunInfoStore by future { LastRunInfoStore(immutableConfig) }
private fun resolveDeviceId(): String? {
val deviceIdStore = DeviceIdStore(
appContext,
sharedPrefMigrator = _sharedPrefMigrator,
logger = logger
)

val sessionStore by future { SessionStore(immutableConfig, logger, null) }
return deviceIdStore.loadDeviceId()
}

val lastRunInfo by future {
private fun resolveLastRunInfo(): LastRunInfo? {
val info = lastRunInfoStore.load()
val currentRunInfo = LastRunInfo(0, crashed = false, crashedDuringLaunch = false)
lastRunInfoStore.persist(currentRunInfo)
info
return info
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,58 @@
package com.bugsnag.android.internal.dag

import androidx.annotation.WorkerThread
import com.bugsnag.android.BackgroundTaskService
import com.bugsnag.android.TaskType
import java.util.concurrent.Callable

internal abstract class DependencyModule {

private val properties = mutableListOf<Lazy<*>>()
@Volatile
internal var dependenciesResolved = false

/**
* Creates a new [Lazy] property that is marked as an object that should be resolved off the
* main thread when [resolveDependencies] is called.
*/
fun <T> future(initializer: () -> T): Lazy<T> {
val lazy = lazy {
initializer()
inline fun <R> resolvedValueOf(value: () -> R): R {
synchronized(this) {
while (!dependenciesResolved) {
// The probability that we actually need to wait for the dependencies to be resolved
// is quite low, so we don't want the overhead (especially during startup) or a
// ReentrantLock. Instead we want to use the Java wait() and notify() methods
// so we can leverage monitor locks, which (at time of writing) typically have
// no allocation cost (until there is contention)
// https://android.googlesource.com/platform/art/+/master/runtime/monitor.cc#57
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
(this as Object).wait()
}
}
properties.add(lazy)
return lazy

return value()
}

@WorkerThread
protected open fun resolveDependencies() {
}

/**
* Blocks until all dependencies in the module have been constructed. This provides the option
* for modules to construct objects in a background thread, then have a user block on another
* thread until all the objects have been constructed.
*/
fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
kotlin.runCatching {
open fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
try {
bgTaskService.submitTask(
taskType,
Runnable {
properties.forEach { it.value }
// Callable<Unit> avoids wrapping the Runnable in a Callable
Callable {
synchronized(this) {
dependenciesResolved = true
resolveDependencies()

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
(this as Object).notifyAll()
}
}
).get()
)
} catch (exception: Exception) {
// ignore failures
}
}
}

0 comments on commit 3270ef5

Please sign in to comment.