Skip to content

Commit

Permalink
refactor: create feature registry for data source states
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Jul 3, 2024
1 parent b5a8a8f commit 9e231ca
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,38 @@ import io.embrace.android.embracesdk.arch.datasource.DataSourceState
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorType
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.logging.EmbLogger
import java.util.concurrent.CopyOnWriteArrayList

/**
* Orchestrates all data sources that could potentially be used in the SDK. This is a convenient
* place to coordinate everything in one place.
*/
internal class DataCaptureOrchestrator(
private val dataSourceState: List<DataSourceState<*>>,
private val logger: EmbLogger,
configService: ConfigService
) {
configService: ConfigService,
private val logger: EmbLogger
) : EmbraceFeatureRegistry {

init {
configService.addListener {
onConfigChange()
}
}

private val dataSourceStates = CopyOnWriteArrayList<DataSourceState<*>>()

var currentSessionType: SessionType? = null

Check warning on line 26 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/DataCaptureOrchestrator.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/DataCaptureOrchestrator.kt#L26

Added line #L26 was not covered by tests
set(value) {
field = value
onSessionTypeChange()
}

override fun add(state: DataSourceState<*>) {
dataSourceStates.add(state)
state.currentSessionType = currentSessionType
}

private fun onConfigChange() {
dataSourceState.forEach { state ->
dataSourceStates.forEach { state ->
try {
state.onConfigChange()
} catch (exc: Throwable) {
Expand All @@ -35,11 +48,11 @@ internal class DataCaptureOrchestrator(
/**
* Callback that is invoked when the session type changes.
*/
fun onSessionTypeChange(sessionType: SessionType) {
dataSourceState.forEach { state ->
private fun onSessionTypeChange() {
dataSourceStates.forEach { state ->
try {
// alter the session type - some data sources don't capture for background activities.
state.onSessionTypeChange(sessionType)
state.currentSessionType = currentSessionType
} catch (exc: Throwable) {
logger.logError("Exception thrown starting data capture", exc)
logger.trackInternalError(InternalErrorType.SESSION_CHANGE_DATA_CAPTURE_FAIL, exc)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.embrace.android.embracesdk.arch

import io.embrace.android.embracesdk.arch.datasource.DataSourceState

/**
* Registry for all features whose instrumentation should be orchestrated by the Embrace SDK.
*/
internal interface EmbraceFeatureRegistry {

/**
* Adds a feature to the registry. The SDK will control when a feature is enabled/disabled
* based on the declared values in the [state] parameter.
*/
fun add(state: DataSourceState<*>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ internal class DataSourceState<T : DataSource<*>>(
*/
private val configGate: Provider<Boolean> = { true },

/**
* The type of session that contains the data.
*/
private var currentSessionType: SessionType? = null,

/**
* A session type where data capture should be disabled. For example,
* background activities capture a subset of sessions.
*/
private val disabledSessionType: SessionType? = null
) {

/**
* The type of session that contains the data.
*/
var currentSessionType: SessionType? = null

Check warning on line 36 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/datasource/DataSourceState.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/datasource/DataSourceState.kt#L36

Added line #L36 was not covered by tests
set(value) {
field = value
onSessionTypeChange()
}

private val enabledDataSource by lazy(factory)

var dataSource: T? = null
Expand All @@ -47,8 +51,7 @@ internal class DataSourceState<T : DataSource<*>>(
/**
* Callback that is invoked when the session type changes.
*/
fun onSessionTypeChange(sessionType: SessionType?) {
this.currentSessionType = sessionType
private fun onSessionTypeChange() {
updateDataSource()
enabledDataSource?.resetDataCaptureLimits()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.injection
import android.os.Build
import io.embrace.android.embracesdk.anr.sigquit.SigquitDataSource
import io.embrace.android.embracesdk.arch.DataCaptureOrchestrator
import io.embrace.android.embracesdk.arch.EmbraceFeatureRegistry
import io.embrace.android.embracesdk.arch.datasource.DataSource
import io.embrace.android.embracesdk.arch.datasource.DataSourceState
import io.embrace.android.embracesdk.capture.aei.AeiDataSource
Expand Down Expand Up @@ -38,11 +39,8 @@ import kotlin.reflect.KProperty
* Data will then automatically be captured by the SDK.
*/
internal interface DataSourceModule {
/**
* Returns a list of all the data sources that are defined in this module.
*/
fun getDataSources(): List<DataSourceState<*>>

val embraceFeatureRegistry: EmbraceFeatureRegistry
val dataCaptureOrchestrator: DataCaptureOrchestrator
val breadcrumbDataSource: DataSourceState<BreadcrumbDataSource>
val viewDataSource: DataSourceState<ViewDataSource>
val tapDataSource: DataSourceState<TapDataSource>
Expand Down Expand Up @@ -71,13 +69,19 @@ internal class DataSourceModuleImpl(
anrModule: AnrModule
) : DataSourceModule {

private val values: MutableList<DataSourceState<*>> = mutableListOf()
private val configService = essentialServiceModule.configService

override val dataCaptureOrchestrator: DataCaptureOrchestrator by singleton {
DataCaptureOrchestrator(configService, initModule.logger)
}

override val embraceFeatureRegistry: EmbraceFeatureRegistry = dataCaptureOrchestrator

override val breadcrumbDataSource: DataSourceState<BreadcrumbDataSource> by dataSourceState {
DataSourceState(
factory = {
BreadcrumbDataSource(
breadcrumbBehavior = essentialServiceModule.configService.breadcrumbBehavior,
breadcrumbBehavior = configService.breadcrumbBehavior,
writer = otelModule.currentSessionSpan,
logger = initModule.logger
)
Expand All @@ -89,7 +93,7 @@ internal class DataSourceModuleImpl(
DataSourceState(
factory = {
TapDataSource(
breadcrumbBehavior = essentialServiceModule.configService.breadcrumbBehavior,
breadcrumbBehavior = configService.breadcrumbBehavior,
writer = otelModule.currentSessionSpan,
logger = initModule.logger
)
Expand All @@ -101,7 +105,7 @@ internal class DataSourceModuleImpl(
DataSourceState(
factory = {
PushNotificationDataSource(
breadcrumbBehavior = essentialServiceModule.configService.breadcrumbBehavior,
breadcrumbBehavior = configService.breadcrumbBehavior,
initModule.clock,
writer = otelModule.currentSessionSpan,
logger = initModule.logger
Expand Down Expand Up @@ -160,18 +164,11 @@ internal class DataSourceModuleImpl(
)
}

override val applicationExitInfoDataSource: DataSourceState<AeiDataSource>? by dataSourceState {
DataSourceState(
factory = { aeiService },
configGate = { configService.isAppExitInfoCaptureEnabled() }
)
}

private val aeiService: AeiDataSourceImpl? by singleton {
if (BuildVersionChecker.isAtLeast(Build.VERSION_CODES.R)) {
AeiDataSourceImpl(
workerThreadModule.backgroundWorker(WorkerName.BACKGROUND_REGISTRATION),
essentialServiceModule.configService.appExitInfoBehavior,
configService.appExitInfoBehavior,
systemServiceModule.activityManager,
androidServicesModule.preferencesService,
essentialServiceModule.logWriter,
Expand All @@ -182,6 +179,13 @@ internal class DataSourceModuleImpl(
}
}

override val applicationExitInfoDataSource: DataSourceState<AeiDataSource>? by dataSourceState {
DataSourceState(
factory = { aeiService },
configGate = { configService.isAppExitInfoCaptureEnabled() }
)
}

override val lowPowerDataSource: DataSourceState<LowPowerDataSource> by dataSourceState {
DataSourceState(
factory = {
Expand Down Expand Up @@ -257,7 +261,7 @@ internal class DataSourceModuleImpl(
DataSourceState(
factory = {
WebViewDataSource(
webViewVitalsBehavior = essentialServiceModule.configService.webViewVitalsBehavior,
webViewVitalsBehavior = configService.webViewVitalsBehavior,
writer = otelModule.currentSessionSpan,
logger = initModule.logger,
serializer = initModule.jsonSerializer
Expand All @@ -279,29 +283,25 @@ internal class DataSourceModuleImpl(
)
}

private val configService = essentialServiceModule.configService

override fun getDataSources(): List<DataSourceState<*>> = values

/**
* Property delegate that adds the value to a
* list on its creation. That list is then used by the [DataCaptureOrchestrator] to control
* the data sources.
*/
@Suppress("unused")
private fun <T : DataSource<*>> dataSourceState(provider: Provider<DataSourceState<T>>) =
DataSourceDelegate(provider = provider, values = values)
DataSourceDelegate(provider, dataCaptureOrchestrator)
}

private class DataSourceDelegate<S : DataSource<*>>(
provider: Provider<DataSourceState<S>>,
values: MutableList<DataSourceState<*>>,
dataCaptureOrchestrator: DataCaptureOrchestrator
) : ReadOnlyProperty<Any?, DataSourceState<S>> {

private val value: DataSourceState<S> = provider()

init {
values.add(value)
dataCaptureOrchestrator.add(value)
}

override fun getValue(thisRef: Any?, property: KProperty<*>) = value
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.embrace.android.embracesdk.injection

import io.embrace.android.embracesdk.arch.DataCaptureOrchestrator
import io.embrace.android.embracesdk.ndk.NativeModule
import io.embrace.android.embracesdk.session.caching.PeriodicBackgroundActivityCacher
import io.embrace.android.embracesdk.session.caching.PeriodicSessionCacher
Expand All @@ -23,7 +22,6 @@ internal interface SessionModule {
val sessionOrchestrator: SessionOrchestrator
val periodicSessionCacher: PeriodicSessionCacher
val periodicBackgroundActivityCacher: PeriodicBackgroundActivityCacher
val dataCaptureOrchestrator: DataCaptureOrchestrator
}

internal class SessionModuleImpl(
Expand Down Expand Up @@ -94,11 +92,6 @@ internal class SessionModuleImpl(
)
}

override val dataCaptureOrchestrator: DataCaptureOrchestrator by singleton {
val dataSources = dataSourceModule.getDataSources()
DataCaptureOrchestrator(dataSources, initModule.logger, essentialServiceModule.configService)
}

private val sessionSpanAttrPopulator by singleton {
SessionSpanAttrPopulator(
openTelemetryModule.currentSessionSpan,
Expand All @@ -120,7 +113,7 @@ internal class SessionModuleImpl(
deliveryModule.deliveryService,
periodicSessionCacher,
periodicBackgroundActivityCacher,
dataCaptureOrchestrator,
dataSourceModule.dataCaptureOrchestrator,
openTelemetryModule.currentSessionSpan,
sessionSpanAttrPopulator,
initModule.logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ internal class SessionOrchestratorImpl(
ProcessState.FOREGROUND -> SessionType.FOREGROUND
ProcessState.BACKGROUND -> SessionType.BACKGROUND
}
dataCaptureOrchestrator.onSessionTypeChange(sessionType)
dataCaptureOrchestrator.currentSessionType = sessionType

// log the state change
logSessionStateChange(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class EmbraceMemoryServiceTest {
workerThreadModule = FakeWorkerThreadModule(),
anrModule = FakeAnrModule()
)
dataSourceModule.getDataSources().forEach { it.onSessionTypeChange(SessionType.FOREGROUND) }
dataSourceModule.dataCaptureOrchestrator.currentSessionType = SessionType.FOREGROUND
embraceMemoryService = EmbraceMemoryService(fakeClock) { dataSourceModule }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ internal class EmbraceWebViewServiceTest {
dataSourceModule = fakeDataSourceModule(
oTelModule = openTelemetryModule,
).apply {
getDataSources().forEach {
it.onSessionTypeChange(SessionType.FOREGROUND)
}
dataCaptureOrchestrator.currentSessionType = SessionType.FOREGROUND
}
cfg = RemoteConfig(webViewVitals = WebViewVitals(100f, 50))
configService = FakeConfigService(webViewVitalsBehavior = fakeWebViewVitalsBehavior { cfg })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ internal class DataCaptureOrchestratorTest {
dataSource = FakeDataSource(mockContext())
configService = FakeConfigService()
orchestrator = DataCaptureOrchestrator(
listOf(
configService,
EmbLoggerImpl(),
).apply {
add(
DataSourceState(
factory = { dataSource },
configGate = { enabled },
currentSessionType = null
configGate = { enabled }
)
),
EmbLoggerImpl(),
configService
)
)
}
}

@Test
fun `config changes are propagated`() {
assertEquals(0, dataSource.enableDataCaptureCount)
orchestrator.onSessionTypeChange(SessionType.FOREGROUND)
orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(1, dataSource.enableDataCaptureCount)

enabled = false
Expand All @@ -47,7 +47,7 @@ internal class DataCaptureOrchestratorTest {
@Test
fun `session type change is propagated`() {
assertEquals(0, dataSource.enableDataCaptureCount)
orchestrator.onSessionTypeChange(SessionType.FOREGROUND)
orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(1, dataSource.enableDataCaptureCount)
}
}
Loading

0 comments on commit 9e231ca

Please sign in to comment.