Skip to content

Commit

Permalink
Merge branch 'rz/feat/session-replay' into rz/feat/session-replay-tou…
Browse files Browse the repository at this point in the history
…ch-events
  • Loading branch information
romtsn committed May 6, 2024
2 parents 1217bb1 + d4ac484 commit 647822c
Show file tree
Hide file tree
Showing 29 changed files with 327 additions and 119 deletions.
15 changes: 13 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# Changelog

## 7.8.0-alpha.0
## 7.9.0-alpha.1

- No documented changes.
- Session Replay for Android ([#3339](https://github.com/getsentry/sentry-java/pull/3339))

We released our second Alpha version of the SDK with support. To get access, it requires adding your Sentry org to our feature flag. Please let us know on the [waitlist](https://sentry.io/lp/mobile-replay-beta/) if you're interested

### Features

- Add start_type to app context ([#3379](https://github.com/getsentry/sentry-java/pull/3379))

### Fixes

- Fix Frame measurements in app start transactions ([#3382](https://github.com/getsentry/sentry-java/pull/3382))
- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368))

## 7.8.0

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android.useAndroidX=true
android.defaults.buildfeatures.buildconfig=true

# Release information
versionName=7.8.0-alpha.0
versionName=7.9.0-alpha.1

# Override the SDK name on native crashes on Android
sentryAndroidSdkName=sentry.native.android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import io.sentry.protocol.App;
import io.sentry.protocol.MeasurementValue;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentrySpan;
Expand Down Expand Up @@ -79,27 +80,40 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {

// the app start measurement is only sent once and only if the transaction has
// the app.start span, which is automatically created by the SDK.
if (!sentStartMeasurement && hasAppStartSpan(transaction)) {
final @NotNull TimeSpan appStartTimeSpan =
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();

// if appStartUpDurationMs is 0, metrics are not ready to be sent
if (appStartUpDurationMs != 0) {
final MeasurementValue value =
new MeasurementValue(
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());

final String appStartKey =
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
? MeasurementValue.KEY_APP_START_COLD
: MeasurementValue.KEY_APP_START_WARM;

transaction.getMeasurements().put(appStartKey, value);

attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
sentStartMeasurement = true;
if (hasAppStartSpan(transaction)) {
if (!sentStartMeasurement) {
final @NotNull TimeSpan appStartTimeSpan =
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();

// if appStartUpDurationMs is 0, metrics are not ready to be sent
if (appStartUpDurationMs != 0) {
final MeasurementValue value =
new MeasurementValue(
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());

final String appStartKey =
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
? MeasurementValue.KEY_APP_START_COLD
: MeasurementValue.KEY_APP_START_WARM;

transaction.getMeasurements().put(appStartKey, value);

attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
sentStartMeasurement = true;
}
}

@Nullable App appContext = transaction.getContexts().getApp();
if (appContext == null) {
appContext = new App();
transaction.getContexts().setApp(appContext);
}
final String appStartType =
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
? "cold"
: "warm";
appContext.setStartType(appStartType);
}

final SentryId eventId = transaction.getEventId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.sentry.NoOpTransaction;
import io.sentry.SentryDate;
import io.sentry.SentryNanotimeDate;
import io.sentry.SentryTracer;
import io.sentry.SpanDataConvention;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.protocol.MeasurementValue;
Expand Down Expand Up @@ -135,11 +136,15 @@ private void captureFrameMetrics(@NotNull final ISpan span) {
return;
}

// ignore spans with no finish date
final @Nullable SentryDate spanFinishDate = span.getFinishDate();
// Ignore spans with no finish date, but SentryTracer is not finished when executing this
// callback, yet, so in that case we use the current timestamp.
final @Nullable SentryDate spanFinishDate =
span instanceof SentryTracer ? new SentryNanotimeDate() : span.getFinishDate();
if (spanFinishDate == null) {
return;
}
// Note: The comparison between two values obtained by realNanos() works only if both are the
// same kind of dates (both are SentryNanotimeDate or both SentryLongDate)
final long spanEndNanos = realNanos(spanFinishDate);

final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import io.sentry.DateUtils;
import io.sentry.SentryDate;
import io.sentry.SentryLongDate;
import io.sentry.SentryNanotimeDate;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -21,6 +23,7 @@ public class TimeSpan implements Comparable<TimeSpan> {

private @Nullable String description;

private long startSystemNanos;
private long startUnixTimeMs;
private long startUptimeMs;
private long stopUptimeMs;
Expand All @@ -29,6 +32,7 @@ public class TimeSpan implements Comparable<TimeSpan> {
public void start() {
startUptimeMs = SystemClock.uptimeMillis();
startUnixTimeMs = System.currentTimeMillis();
startSystemNanos = System.nanoTime();
}

/**
Expand All @@ -40,6 +44,7 @@ public void setStartedAt(final long uptimeMs) {

final long shiftMs = SystemClock.uptimeMillis() - startUptimeMs;
startUnixTimeMs = System.currentTimeMillis() - shiftMs;
startSystemNanos = System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(shiftMs);
}

/** Stops the time span */
Expand Down Expand Up @@ -90,7 +95,8 @@ public long getStartTimestampMs() {
*/
public @Nullable SentryDate getStartTimestamp() {
if (hasStarted()) {
return new SentryLongDate(DateUtils.millisToNanos(getStartTimestampMs()));
return new SentryNanotimeDate(
DateUtils.nanosToDate(DateUtils.millisToNanos(getStartTimestampMs())), startSystemNanos);
}
return null;
}
Expand Down Expand Up @@ -162,6 +168,7 @@ public void reset() {
startUptimeMs = 0;
stopUptimeMs = 0;
startUnixTimeMs = 0;
startSystemNanos = 0;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
Expand Down Expand Up @@ -464,6 +465,60 @@ class PerformanceAndroidEventProcessorTest {
}
}

@Test
fun `does not set start_type field for txns without app start span`() {
// given some ui.load txn
setAppStart(fixture.options, coldStart = true)

val sut = fixture.getSut(enablePerformanceV2 = true)
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)

// when it contains no app start span and is processed
tr = sut.process(tr, Hint())

// start_type should not be set
assertNull(tr.contexts.app?.startType)
}

@Test
fun `sets start_type field for app context`() {
// given some cold app start
setAppStart(fixture.options, coldStart = true)

val sut = fixture.getSut(enablePerformanceV2 = true)
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)

val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
tr.spans.add(appStartSpan)

// when the processor attaches the app start spans
tr = sut.process(tr, Hint())

// start_type should be set as well
assertEquals(
"cold",
tr.contexts.app!!.startType
)
}

private fun setAppStart(options: SentryAndroidOptions, coldStart: Boolean = true) {
AppStartMetrics.getInstance().apply {
appStartType = when (coldStart) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ class SpanFrameMetricsCollectorTest {
options.frameMetricsCollector = frameMetricsCollector
options.isEnableFramesTracking = enabled
options.isEnablePerformanceV2 = enabled
options.setDateProvider {
SentryLongDate(timeNanos)
}
options.dateProvider = SentryAndroidDateProvider()

return SpanFrameMetricsCollector(options, frameMetricsCollector)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ package io.sentry.uitest.android

import androidx.lifecycle.Lifecycle
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Sentry
import io.sentry.SentryLevel
import io.sentry.android.core.AndroidLogger
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.assertEnvelopeTransaction
import io.sentry.protocol.MeasurementValue
import io.sentry.protocol.SentryTransaction
import org.junit.Assume
import org.junit.runner.RunWith
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
Expand Down Expand Up @@ -49,4 +59,84 @@ class AutomaticSpansTest : BaseUiTest() {
}
}
}

@Test
fun checkAppStartFramesMeasurements() {
initSentry(true) { options: SentryAndroidOptions ->
options.tracesSampleRate = 1.0
options.isEnableTimeToFullDisplayTracing = true
options.isEnablePerformanceV2 = false
}

IdlingRegistry.getInstance().register(ProfilingSampleActivity.scrollingIdlingResource)
val sampleScenario = launchActivity<ProfilingSampleActivity>()
swipeList(3)
Sentry.reportFullyDisplayed()
sampleScenario.moveToState(Lifecycle.State.DESTROYED)
IdlingRegistry.getInstance().unregister(ProfilingSampleActivity.scrollingIdlingResource)
relayIdlingResource.increment()

relay.assert {
findEnvelope {
assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction == "ProfilingSampleActivity"
}.assert {
val transactionItem: SentryTransaction = it.assertTransaction()
it.assertNoOtherItems()
val measurements = transactionItem.measurements
val frozenFrames = measurements[MeasurementValue.KEY_FRAMES_FROZEN]?.value?.toInt() ?: 0
val slowFrames = measurements[MeasurementValue.KEY_FRAMES_SLOW]?.value?.toInt() ?: 0
val totalFrames = measurements[MeasurementValue.KEY_FRAMES_TOTAL]?.value?.toInt() ?: 0
assertEquals("ProfilingSampleActivity", transactionItem.transaction)
// AGP matrix tests have no frames
Assume.assumeTrue(totalFrames > 0)
assertNotEquals(totalFrames, 0)
assertTrue(totalFrames > slowFrames + frozenFrames, "Expected total frames ($totalFrames) to be higher than the sum of slow ($slowFrames) and frozen ($frozenFrames) frames.")
}
assertNoOtherEnvelopes()
}
}

@Test
fun checkAppStartFramesMeasurementsPerfV2() {
initSentry(true) { options: SentryAndroidOptions ->
options.tracesSampleRate = 1.0
options.isEnableTimeToFullDisplayTracing = true
options.isEnablePerformanceV2 = true
}

IdlingRegistry.getInstance().register(ProfilingSampleActivity.scrollingIdlingResource)
val sampleScenario = launchActivity<ProfilingSampleActivity>()
swipeList(3)
Sentry.reportFullyDisplayed()
sampleScenario.moveToState(Lifecycle.State.DESTROYED)
IdlingRegistry.getInstance().unregister(ProfilingSampleActivity.scrollingIdlingResource)
relayIdlingResource.increment()

relay.assert {
findEnvelope {
assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction == "ProfilingSampleActivity"
}.assert {
val transactionItem: SentryTransaction = it.assertTransaction()
it.assertNoOtherItems()
val measurements = transactionItem.measurements
val frozenFrames = measurements[MeasurementValue.KEY_FRAMES_FROZEN]?.value?.toInt() ?: 0
val slowFrames = measurements[MeasurementValue.KEY_FRAMES_SLOW]?.value?.toInt() ?: 0
val totalFrames = measurements[MeasurementValue.KEY_FRAMES_TOTAL]?.value?.toInt() ?: 0
assertEquals("ProfilingSampleActivity", transactionItem.transaction)
// AGP matrix tests have no frames
Assume.assumeTrue(totalFrames > 0)
assertNotEquals(totalFrames, 0)
assertTrue(totalFrames > slowFrames + frozenFrames, "Expected total frames ($totalFrames) to be higher than the sum of slow ($slowFrames) and frozen ($frozenFrames) frames.")
}
assertNoOtherEnvelopes()
}
}

private fun swipeList(times: Int) {
repeat(times) {
Thread.sleep(100)
Espresso.onView(ViewMatchers.withId(R.id.profiling_sample_list)).perform(ViewActions.swipeUp())
Espresso.onIdle()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class EnvelopeTests : BaseUiTest() {
// Timestamps of measurements should differ at least 10 milliseconds from each other
(1 until values.size).forEach { i ->
assertTrue(
values[i].relativeStartNs.toLong() > values[i - 1].relativeStartNs.toLong() + TimeUnit.MILLISECONDS.toNanos(
values[i].relativeStartNs.toLong() >= values[i - 1].relativeStartNs.toLong() + TimeUnit.MILLISECONDS.toNanos(
10
),
"Measurement value timestamp for '$name' does not differ at least 10ms"
Expand Down
Loading

0 comments on commit 647822c

Please sign in to comment.