-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #491 from embrace-io/add-view-crumb-spans
Add fragment breadcrumb data source
- Loading branch information
Showing
4 changed files
with
218 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 54 additions & 61 deletions
115
...rc/main/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,83 @@ | ||
package io.embrace.android.embracesdk.capture.crumbs | ||
|
||
import io.embrace.android.embracesdk.arch.DataCaptureService | ||
import io.embrace.android.embracesdk.arch.datasource.NoInputValidation | ||
import io.embrace.android.embracesdk.arch.datasource.SpanDataSourceImpl | ||
import io.embrace.android.embracesdk.arch.datasource.startSpanCapture | ||
import io.embrace.android.embracesdk.arch.destination.StartSpanData | ||
import io.embrace.android.embracesdk.arch.destination.StartSpanMapper | ||
import io.embrace.android.embracesdk.arch.limits.UpToLimitStrategy | ||
import io.embrace.android.embracesdk.config.ConfigService | ||
import io.embrace.android.embracesdk.internal.clock.Clock | ||
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger | ||
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger | ||
import io.embrace.android.embracesdk.internal.spans.SpanService | ||
import io.embrace.android.embracesdk.payload.FragmentBreadcrumb | ||
import io.embrace.android.embracesdk.utils.filter | ||
import java.util.Collections | ||
import io.embrace.android.embracesdk.spans.EmbraceSpan | ||
|
||
/** | ||
* Captures fragment breadcrumbs. | ||
*/ | ||
internal class FragmentBreadcrumbDataSource( | ||
private val configService: ConfigService, | ||
configService: ConfigService, | ||
private val clock: Clock, | ||
private val store: BreadcrumbDataStore<FragmentBreadcrumb> = BreadcrumbDataStore { | ||
configService.breadcrumbBehavior.getFragmentBreadcrumbLimit() | ||
}, | ||
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger | ||
) : DataCaptureService<List<FragmentBreadcrumb>> by store { | ||
spanService: SpanService | ||
) : SpanDataSourceImpl( | ||
spanService, | ||
UpToLimitStrategy({ configService.breadcrumbBehavior.getFragmentBreadcrumbLimit() }) | ||
), | ||
StartSpanMapper<FragmentBreadcrumb> { | ||
|
||
companion object { | ||
|
||
/** | ||
* The default limit for how many open tracked fragments are allowed, which can be overridden | ||
* by [RemoteConfig]. | ||
*/ | ||
private const val DEFAULT_VIEW_STACK_SIZE = 20 | ||
internal const val TYPE_NAME = "ux.view" | ||
internal const val SPAN_NAME = "screen-view" | ||
} | ||
|
||
internal val fragmentStack: MutableList<FragmentBreadcrumb> = Collections.synchronizedList(ArrayList<FragmentBreadcrumb>()) | ||
private val fragmentSpans: MutableMap<String, EmbraceSpan> = mutableMapOf() | ||
|
||
fun startFragment(name: String?): Boolean { | ||
if (name == null) { | ||
return false | ||
} | ||
synchronized(this) { | ||
if (fragmentStack.size >= DEFAULT_VIEW_STACK_SIZE) { | ||
return false | ||
/** | ||
* Called when a fragment is started. | ||
*/ | ||
fun startFragment(name: String?): Boolean = captureSpanData( | ||
countsTowardsLimits = true, | ||
inputValidation = { !name.isNullOrEmpty() }, | ||
captureAction = { | ||
val crumb = FragmentBreadcrumb(checkNotNull(name), clock.now()) | ||
startSpanCapture(crumb, ::toStartSpanData)?.apply { | ||
fragmentSpans[name] = this | ||
} | ||
return fragmentStack.add(FragmentBreadcrumb(name, clock.now(), 0)) | ||
} | ||
} | ||
) | ||
|
||
fun endFragment(name: String?): Boolean { | ||
if (name == null) { | ||
return false | ||
} | ||
var start: FragmentBreadcrumb | ||
val end = FragmentBreadcrumb(name, 0, clock.now()) | ||
synchronized(this) { | ||
val crumbs = filter(fragmentStack) { crumb: FragmentBreadcrumb -> crumb.name == name } | ||
if (crumbs.isEmpty()) { | ||
return false | ||
} | ||
start = crumbs[0] | ||
fragmentStack.remove(start) | ||
/** | ||
* Called when a fragment is ended. | ||
*/ | ||
fun endFragment(name: String?): Boolean = captureSpanData( | ||
countsTowardsLimits = false, | ||
inputValidation = { !name.isNullOrEmpty() }, | ||
captureAction = { | ||
fragmentSpans.remove(name)?.stop() | ||
} | ||
end.setStartTime(start.getStartTime()) | ||
store.tryAddBreadcrumb(end) | ||
return true | ||
} | ||
) | ||
|
||
/** | ||
* Close all open fragments when the activity closes | ||
* Called when the activity is closed (and therefore all fragments are assumed to close). | ||
*/ | ||
fun onViewClose() { | ||
if (!configService.breadcrumbBehavior.isActivityBreadcrumbCaptureEnabled()) { | ||
return | ||
} | ||
if (fragmentStack.size == 0) { | ||
return | ||
} | ||
val ts = clock.now() | ||
synchronized(fragmentStack) { | ||
for (fragment in fragmentStack) { | ||
fragment.endTime = ts | ||
store.tryAddBreadcrumb(fragment) | ||
} | ||
fragmentStack.clear() | ||
fragmentSpans.forEach { (_, span) -> | ||
captureSpanData( | ||
countsTowardsLimits = false, | ||
inputValidation = NoInputValidation, | ||
captureAction = { | ||
span.stop() | ||
} | ||
) | ||
} | ||
} | ||
|
||
override fun cleanCollections() { | ||
store.cleanCollections() | ||
fragmentStack.clear() | ||
override fun toStartSpanData(obj: FragmentBreadcrumb): StartSpanData = with(obj) { | ||
StartSpanData( | ||
embType = TYPE_NAME, | ||
spanName = SPAN_NAME, | ||
spanStartTimeMs = start, | ||
attributes = mapOf("view.name" to name) | ||
) | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
...n/java/io/embrace/android/embracesdk/capture/crumbs/LegacyFragmentBreadcrumbDataSource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package io.embrace.android.embracesdk.capture.crumbs | ||
|
||
import io.embrace.android.embracesdk.arch.DataCaptureService | ||
import io.embrace.android.embracesdk.config.ConfigService | ||
import io.embrace.android.embracesdk.internal.clock.Clock | ||
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger | ||
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger | ||
import io.embrace.android.embracesdk.payload.FragmentBreadcrumb | ||
import io.embrace.android.embracesdk.utils.filter | ||
import java.util.Collections | ||
|
||
/** | ||
* Captures fragment breadcrumbs. | ||
*/ | ||
internal class LegacyFragmentBreadcrumbDataSource( | ||
private val configService: ConfigService, | ||
private val clock: Clock, | ||
private val store: BreadcrumbDataStore<FragmentBreadcrumb> = BreadcrumbDataStore { | ||
configService.breadcrumbBehavior.getFragmentBreadcrumbLimit() | ||
}, | ||
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger | ||
) : DataCaptureService<List<FragmentBreadcrumb>> by store { | ||
|
||
companion object { | ||
|
||
/** | ||
* The default limit for how many open tracked fragments are allowed, which can be overridden | ||
* by [RemoteConfig]. | ||
*/ | ||
private const val DEFAULT_VIEW_STACK_SIZE = 20 | ||
} | ||
|
||
internal val fragmentStack: MutableList<FragmentBreadcrumb> = Collections.synchronizedList(ArrayList<FragmentBreadcrumb>()) | ||
|
||
fun startFragment(name: String?): Boolean { | ||
if (name == null) { | ||
return false | ||
} | ||
synchronized(this) { | ||
if (fragmentStack.size >= DEFAULT_VIEW_STACK_SIZE) { | ||
return false | ||
} | ||
return fragmentStack.add(FragmentBreadcrumb(name, clock.now(), 0)) | ||
} | ||
} | ||
|
||
fun endFragment(name: String?): Boolean { | ||
if (name == null) { | ||
return false | ||
} | ||
var start: FragmentBreadcrumb | ||
val end = FragmentBreadcrumb(name, 0, clock.now()) | ||
synchronized(this) { | ||
val crumbs = filter(fragmentStack) { crumb: FragmentBreadcrumb -> crumb.name == name } | ||
if (crumbs.isEmpty()) { | ||
return false | ||
} | ||
start = crumbs[0] | ||
fragmentStack.remove(start) | ||
} | ||
end.setStartTime(start.getStartTime()) | ||
store.tryAddBreadcrumb(end) | ||
return true | ||
} | ||
|
||
/** | ||
* Close all open fragments when the activity closes | ||
*/ | ||
fun onViewClose() { | ||
if (!configService.breadcrumbBehavior.isActivityBreadcrumbCaptureEnabled()) { | ||
return | ||
} | ||
if (fragmentStack.size == 0) { | ||
return | ||
} | ||
val ts = clock.now() | ||
synchronized(fragmentStack) { | ||
for (fragment in fragmentStack) { | ||
fragment.endTime = ts | ||
store.tryAddBreadcrumb(fragment) | ||
} | ||
fragmentStack.clear() | ||
} | ||
} | ||
|
||
override fun cleanCollections() { | ||
store.cleanCollections() | ||
fragmentStack.clear() | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...est/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSourceTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package io.embrace.android.embracesdk.capture.crumbs | ||
|
||
import io.embrace.android.embracesdk.fakes.FakeClock | ||
import io.embrace.android.embracesdk.fakes.FakeConfigService | ||
import io.embrace.android.embracesdk.fakes.FakeSpanService | ||
import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Before | ||
import org.junit.Test | ||
|
||
internal class FragmentBreadcrumbDataSourceTest { | ||
|
||
private lateinit var configService: FakeConfigService | ||
private lateinit var clock: FakeClock | ||
private lateinit var spanService: FakeSpanService | ||
private lateinit var dataSource: FragmentBreadcrumbDataSource | ||
|
||
@Before | ||
fun setUp() { | ||
configService = FakeConfigService() | ||
clock = FakeClock() | ||
spanService = FakeSpanService() | ||
dataSource = FragmentBreadcrumbDataSource( | ||
configService, | ||
clock, | ||
spanService, | ||
) | ||
} | ||
|
||
@Test | ||
fun `fragment with start`() { | ||
dataSource.startFragment("my_fragment") | ||
|
||
val span = spanService.createdSpans.single() | ||
assertEquals("screen-view", span.name) | ||
assertEquals(EmbraceAttributes.Type.PERFORMANCE, span.type) | ||
assertTrue(span.isRecording) | ||
assertEquals( | ||
mapOf( | ||
"view.name" to "my_fragment", | ||
"emb.type" to "ux.view" | ||
), | ||
span.attributes | ||
) | ||
} | ||
|
||
@Test | ||
fun `fragment with start and end`() { | ||
dataSource.startFragment("my_fragment") | ||
clock.tick(30000) | ||
dataSource.endFragment("my_fragment") | ||
|
||
val span = spanService.createdSpans.single() | ||
assertEquals("screen-view", span.name) | ||
assertEquals(EmbraceAttributes.Type.PERFORMANCE, span.type) | ||
assertFalse(span.isRecording) | ||
assertEquals( | ||
mapOf( | ||
"view.name" to "my_fragment", | ||
"emb.type" to "ux.view" | ||
), | ||
span.attributes | ||
) | ||
} | ||
|
||
@Test | ||
fun `end an unknown fragment`() { | ||
assertTrue(dataSource.endFragment("my_fragment")) | ||
assertTrue(spanService.createdSpans.isEmpty()) | ||
} | ||
} |