Skip to content

Commit

Permalink
Merge pull request #542 from embrace-io/span-population
Browse files Browse the repository at this point in the history
Populate spans in new payload
  • Loading branch information
fractalwrench committed Mar 12, 2024
2 parents 1595f6b + 8d864fc commit 98c1673
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
package io.embrace.android.embracesdk.capture.envelope.session

import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService
import io.embrace.android.embracesdk.arch.schema.AppTerminationCause
import io.embrace.android.embracesdk.internal.payload.SessionPayload
import io.embrace.android.embracesdk.internal.payload.Span
import io.embrace.android.embracesdk.internal.payload.toNewPayload
import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.internal.spans.SpanSink
import io.embrace.android.embracesdk.logging.InternalErrorService
import io.embrace.android.embracesdk.session.captureDataSafely
import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType

internal class SessionPayloadSourceImpl(
private val internalErrorService: InternalErrorService,
private val nativeThreadSamplerService: NativeThreadSamplerService?,
private val spanSink: SpanSink,
private val currentSessionSpan: CurrentSessionSpan
) : SessionPayloadSource {

override fun getSessionPayload(endType: SessionSnapshotType) = SessionPayload(
spans = null,
spanSnapshots = null,
internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() },
sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() }
)
override fun getSessionPayload(endType: SessionSnapshotType): SessionPayload {
return SessionPayload(
spans = retrieveSpanData(endType),
spanSnapshots = null,
internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() },
sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() }
)
}

private fun retrieveSpanData(endType: SessionSnapshotType): List<Span>? = captureDataSafely {
when (endType) {
SessionSnapshotType.NORMAL_END -> currentSessionSpan.endSession(null)
SessionSnapshotType.PERIODIC_CACHE -> spanSink.completedSpans()
SessionSnapshotType.JVM_CRASH -> currentSessionSpan.endSession(AppTerminationCause.Crash)
}.map(EmbraceSpanData::toNewPayload)
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
package io.embrace.android.embracesdk.internal.payload

import io.opentelemetry.api.common.Attributes
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.sdk.trace.data.EventData
import io.opentelemetry.sdk.trace.data.SpanData

internal fun SpanData.toNewPayload() = Span(
internal fun EmbraceSpanData.toNewPayload() = Span(
traceId = traceId,
spanId = spanId,
parentSpanId = parentSpanId,
name = name,
startTimeUnixNano = startEpochNanos,
endTimeUnixNano = endEpochNanos,
status = when (status.statusCode) {
startTimeUnixNano = startTimeNanos,
endTimeUnixNano = endTimeNanos,
status = when (status) {
StatusCode.UNSET -> Span.Status.UNSET
StatusCode.OK -> Span.Status.OK
StatusCode.ERROR -> Span.Status.ERROR
else -> Span.Status.UNSET
},
events = events.map(EventData::toNewPayload),
events = events.map(EmbraceSpanEvent::toNewPayload),
attributes = attributes.toNewPayload()
)

internal fun EventData.toNewPayload() = SpanEvent(
internal fun EmbraceSpanEvent.toNewPayload() = SpanEvent(
name = name,
timeUnixNano = epochNanos,
timeUnixNano = timestampNanos,
attributes = attributes.toNewPayload()
)

internal fun Attributes.toNewPayload(): List<Attribute> =
asMap().map { (key, value) -> Attribute(key.key, value.toString()) }
internal fun Map<String, String>.toNewPayload(): List<Attribute> =
map { (key, value) -> Attribute(key, value) }
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.embrace.android.embracesdk.capture.envelope

import io.embrace.android.embracesdk.capture.envelope.session.SessionPayloadSourceImpl
import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan
import io.embrace.android.embracesdk.fakes.FakeInternalErrorService
import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService
import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl
import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType
import org.junit.Test

Expand All @@ -13,7 +15,9 @@ internal class SessionEnvelopeSourceTest {
SessionEnvelopeSource(
SessionPayloadSourceImpl(
FakeInternalErrorService(),
FakeNativeThreadSamplerService()
FakeNativeThreadSamplerService(),
SpanSinkImpl(),
FakeCurrentSessionSpan()
)
).getEnvelope(SessionSnapshotType.NORMAL_END)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package io.embrace.android.embracesdk.capture.envelope.session

import io.embrace.android.embracesdk.fakes.FakeClock
import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan
import io.embrace.android.embracesdk.fakes.FakeInternalErrorService
import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService
import io.embrace.android.embracesdk.fakes.FakeSpanData
import io.embrace.android.embracesdk.internal.payload.SessionPayload
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl
import io.embrace.android.embracesdk.payload.LegacyExceptionError
import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test

internal class SessionPayloadSourceImplTest {

private lateinit var impl: SessionPayloadSourceImpl
private lateinit var sink: SpanSinkImpl
private lateinit var currentSessionSpan: FakeCurrentSessionSpan
private val cacheSpan = FakeSpanData(name = "cache-span")

@Before
fun setUp() {
Expand All @@ -21,33 +29,48 @@ internal class SessionPayloadSourceImplTest {
addException(RuntimeException(), "test", FakeClock())
}
}
sink = SpanSinkImpl().apply {
storeCompletedSpans(listOf(cacheSpan))
}
currentSessionSpan = FakeCurrentSessionSpan().apply {
spanData = listOf(EmbraceSpanData(FakeSpanData("my-span")))
}
impl = SessionPayloadSourceImpl(
errorService,
FakeNativeThreadSamplerService()
FakeNativeThreadSamplerService(),
sink,
currentSessionSpan
)
}

@Test
fun `session crash`() {
val payload = impl.getSessionPayload(SessionSnapshotType.JVM_CRASH)
assertPayloadPopulated(payload)
val span = checkNotNull(payload.spans?.single())
assertEquals("my-span", span.name)
}

@Test
fun `session cache`() {
val payload = impl.getSessionPayload(SessionSnapshotType.PERIODIC_CACHE)
assertPayloadPopulated(payload)
val span = checkNotNull(payload.spans?.single())
assertEquals("cache-span", span.name)
}

@Test
fun `session lifecycle change`() {
val payload = impl.getSessionPayload(SessionSnapshotType.NORMAL_END)
assertPayloadPopulated(payload)
val span = checkNotNull(payload.spans?.single())
assertEquals("my-span", span.name)
}

private fun assertPayloadPopulated(payload: SessionPayload) {
val err = checkNotNull(payload.internalError)
assertEquals(1, err.count)
assertEquals(mapOf("armeabi-v7a" to "my-symbols"), payload.sharedLibSymbolMapping)
assertNull(payload.spanSnapshots)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan {
var initializedCallCount = 0
var addedEvents = mutableListOf<SpanEventData>()
var addedAttributes = mutableListOf<SpanAttributeData>()
var spanData = listOf<EmbraceSpanData>()

override fun initializeService(sdkInitStartTimeMs: Long) {
}
Expand All @@ -32,7 +33,7 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan {
}

override fun endSession(appTerminationCause: AppTerminationCause?): List<EmbraceSpanData> {
return emptyList()
return spanData
}

override fun canStartNewSpan(parent: EmbraceSpan?, internal: Boolean): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
package io.embrace.android.embracesdk.internal.payload

import io.embrace.android.embracesdk.fakes.FakeSpanData
import io.opentelemetry.sdk.trace.data.SpanData
import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import org.junit.Assert.assertEquals
import org.junit.Test

internal class SpanMapperTest {

@Test
fun toSpan() {
val input: SpanData = FakeSpanData()
val input = EmbraceSpanData(FakeSpanData())
val output = input.toNewPayload()

assertEquals(input.traceId, output.traceId)
assertEquals(input.spanId, output.spanId)
assertEquals(input.parentSpanId, output.parentSpanId)
assertEquals(input.name, output.name)
assertEquals(input.startEpochNanos, output.startTimeUnixNano)
assertEquals(input.endEpochNanos, output.endTimeUnixNano)
assertEquals(input.status.statusCode.name, checkNotNull(output.status).name)
assertEquals(input.startTimeNanos, output.startTimeUnixNano)
assertEquals(input.endTimeNanos, output.endTimeUnixNano)
assertEquals(input.status.name, checkNotNull(output.status).name)

// validate event copied
val inputEvent = input.events.single()
val outputEvent = checkNotNull(output.events).single()
assertEquals(inputEvent.name, outputEvent.name)
assertEquals(inputEvent.epochNanos, outputEvent.timeUnixNano)
assertEquals(inputEvent.timestampNanos, outputEvent.timeUnixNano)

// test event attributes
val inputEventAttrs = checkNotNull(inputEvent.attributes?.asMap())
val inputEventAttrs = checkNotNull(inputEvent.attributes)
val outputEventAttrs = checkNotNull(outputEvent.attributes?.single())
assertEquals(inputEventAttrs.keys.single().key, outputEventAttrs.key)
assertEquals(inputEventAttrs.keys.single(), outputEventAttrs.key)
assertEquals(inputEventAttrs.values.single(), outputEventAttrs.data)

// test attributes
val inputAttrs = checkNotNull(input.attributes?.asMap())
val inputAttrs = checkNotNull(input.attributes)
val outputAttrs = checkNotNull(output.attributes?.single())
assertEquals(inputAttrs.keys.single().key, outputAttrs.key)
assertEquals(inputAttrs.keys.single(), outputAttrs.key)
assertEquals(inputAttrs.values.single(), outputAttrs.data)
}
}

0 comments on commit 98c1673

Please sign in to comment.