Skip to content

Commit

Permalink
Memory warning datasource (#731)
Browse files Browse the repository at this point in the history
  • Loading branch information
leandro-godon committed Apr 16, 2024
1 parent 53461e0 commit e9fc580
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.embrace.android.embracesdk.features

import android.app.Application
import android.content.ComponentCallbacks2
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.embrace.android.embracesdk.IntegrationTestRule
import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.findSessionSpan
import io.embrace.android.embracesdk.hasEventOfType
import io.embrace.android.embracesdk.recordSession
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
internal class MemoryWarningFeatureTest {

@Rule
@JvmField
val testRule: IntegrationTestRule = IntegrationTestRule()

@Test
fun `memory warning`() {
val ctx = ApplicationProvider.getApplicationContext<Application>()
with(testRule) {
val message = checkNotNull(harness.recordSession {
ctx.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW)
})
assertTrue(message.findSessionSpan().hasEventOfType(EmbType.Performance.MemoryWarning))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal sealed class EmbType(type: String, subtype: String?) : TelemetryType {
internal object ThreadBlockage : Performance("thread_blockage")

internal object ThreadBlockageSample : Performance("thread_blockage_sample")

internal object MemoryWarning : Performance("memory_warning")
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ internal sealed class SchemaType(
).toNonNullMap()
}

internal class MemoryWarning : SchemaType(EmbType.Performance.MemoryWarning) {
override val attrs = emptyMap<String, String>()
}

internal class AeiLog(message: AppExitInfoData) : SchemaType(EmbType.System.Exit) {
override val attrs = mapOf(
"aei_session_id" to message.sessionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.embrace.android.embracesdk.capture.memory

import io.embrace.android.embracesdk.injection.DataSourceModule
import io.embrace.android.embracesdk.internal.clock.Clock
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.payload.MemoryWarning
import java.util.NavigableMap
import java.util.concurrent.ConcurrentSkipListMap
Expand All @@ -11,15 +13,18 @@ import java.util.concurrent.ConcurrentSkipListMap
* Stores memory warnings when the [ActivityService] detects a memory trim event.
*/
internal class EmbraceMemoryService(
private val clock: Clock
private val clock: Clock,
private val dataSourceModuleProvider: Provider<DataSourceModule?>,
) : MemoryService {

private val memoryTimestamps = LongArray(MAX_CAPTURED_MEMORY_WARNINGS)
private var offset = 0

override fun onMemoryWarning() {
val timestamp = clock.now()
dataSourceModuleProvider()?.memoryWarningDataSource?.dataSource?.onMemoryWarning(timestamp)
if (offset < MAX_CAPTURED_MEMORY_WARNINGS) {
memoryTimestamps[offset] = clock.now()
memoryTimestamps[offset] = timestamp
offset++
}
}
Expand All @@ -37,6 +42,6 @@ internal class EmbraceMemoryService(
}

companion object {
private const val MAX_CAPTURED_MEMORY_WARNINGS = 100
const val MAX_CAPTURED_MEMORY_WARNINGS = 100
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.embrace.android.embracesdk.capture.memory

import io.embrace.android.embracesdk.arch.datasource.DataSourceImpl
import io.embrace.android.embracesdk.arch.destination.SessionSpanWriter
import io.embrace.android.embracesdk.arch.destination.SpanEventData
import io.embrace.android.embracesdk.arch.destination.SpanEventMapper
import io.embrace.android.embracesdk.arch.limits.UpToLimitStrategy
import io.embrace.android.embracesdk.arch.schema.SchemaType
import io.embrace.android.embracesdk.internal.clock.millisToNanos
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.payload.MemoryWarning

/**
* Captures custom breadcrumbs.
*/
internal class MemoryWarningDataSource(
sessionSpanWriter: SessionSpanWriter,
logger: InternalEmbraceLogger
) : DataSourceImpl<SessionSpanWriter>(
destination = sessionSpanWriter,
logger = logger,
limitStrategy = UpToLimitStrategy(logger) { EmbraceMemoryService.MAX_CAPTURED_MEMORY_WARNINGS }
),
SpanEventMapper<MemoryWarning> {

fun onMemoryWarning(timestamp: Long) {
alterSessionSpan(
inputValidation = { true },
captureAction = {
val memoryWarning = MemoryWarning(timestamp)
addEvent(memoryWarning, ::toSpanEventData)
}
)
}

override fun toSpanEventData(obj: MemoryWarning): SpanEventData {
return SpanEventData(
SchemaType.MemoryWarning(),
obj.timestamp.millisToNanos()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(

override val memoryService: MemoryService by singleton {
if (configService.autoDataCaptureBehavior.isMemoryServiceEnabled()) {
EmbraceMemoryService(initModule.clock)
EmbraceMemoryService(initModule.clock) { dataSourceModule }
} else {
NoOpMemoryService()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.embrace.android.embracesdk.capture.crumbs.PushNotificationDataSource
import io.embrace.android.embracesdk.capture.crumbs.TapDataSource
import io.embrace.android.embracesdk.capture.crumbs.ViewDataSource
import io.embrace.android.embracesdk.capture.crumbs.WebViewUrlDataSource
import io.embrace.android.embracesdk.capture.memory.MemoryWarningDataSource
import io.embrace.android.embracesdk.capture.powersave.LowPowerDataSource
import io.embrace.android.embracesdk.capture.session.SessionPropertiesDataSource
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
Expand Down Expand Up @@ -42,6 +43,7 @@ internal interface DataSourceModule {
val sessionPropertiesDataSource: DataSourceState<SessionPropertiesDataSource>
val applicationExitInfoDataSource: DataSourceState<AeiDataSource>?
val lowPowerDataSource: DataSourceState<LowPowerDataSource>
val memoryWarningDataSource: DataSourceState<MemoryWarningDataSource>
}

internal class DataSourceModuleImpl(
Expand Down Expand Up @@ -132,6 +134,18 @@ internal class DataSourceModuleImpl(
)
}

override val memoryWarningDataSource: DataSourceState<MemoryWarningDataSource> by dataSourceState {
DataSourceState(
factory = {
MemoryWarningDataSource(
sessionSpanWriter = otelModule.currentSessionSpan,
logger = initModule.logger,
)
},
configGate = { configService.autoDataCaptureBehavior.isMemoryServiceEnabled() }
)
}

/* Implementation details */

override val applicationExitInfoDataSource: DataSourceState<AeiDataSource>? by dataSourceState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package io.embrace.android.embracesdk

import io.embrace.android.embracesdk.capture.memory.EmbraceMemoryService
import io.embrace.android.embracesdk.fakes.FakeClock
import io.embrace.android.embracesdk.fakes.FakeOpenTelemetryModule
import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule
import io.embrace.android.embracesdk.fakes.injection.FakeCoreModule
import io.embrace.android.embracesdk.fakes.injection.FakeEssentialServiceModule
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
import io.embrace.android.embracesdk.fakes.injection.FakeSystemServiceModule
import io.embrace.android.embracesdk.fakes.injection.FakeWorkerThreadModule
import io.embrace.android.embracesdk.injection.DataSourceModule
import io.embrace.android.embracesdk.injection.DataSourceModuleImpl
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.assertEquals
Expand All @@ -12,11 +21,22 @@ internal class EmbraceMemoryServiceTest {

private lateinit var embraceMemoryService: EmbraceMemoryService
private val fakeClock = FakeClock()
private val otelModule = FakeOpenTelemetryModule()
private lateinit var dataSourceModule: DataSourceModule

@Before
fun setUp() {
fakeClock.setCurrentTime(100L)
embraceMemoryService = EmbraceMemoryService(fakeClock)
dataSourceModule = DataSourceModuleImpl(
initModule = FakeInitModule(),
coreModule = FakeCoreModule(),
otelModule = otelModule,
essentialServiceModule = FakeEssentialServiceModule(),
systemServiceModule = FakeSystemServiceModule(),
androidServicesModule = FakeAndroidServicesModule(),
workerThreadModule = FakeWorkerThreadModule(),
)
embraceMemoryService = EmbraceMemoryService(fakeClock) { dataSourceModule }
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal class DataSourceModuleImplTest {
assertNotNull(module.sessionPropertiesDataSource)
assertNotNull(module.applicationExitInfoDataSource)
assertNotNull(module.lowPowerDataSource)
assertEquals(8, module.getDataSources().size)
assertNotNull(module.memoryWarningDataSource)
assertEquals(9, module.getDataSources().size)
}
}

0 comments on commit e9fc580

Please sign in to comment.