Skip to content

Commit 1f2d580

Browse files
committed
Send metrics along UI event transactions
1 parent bdcd818 commit 1f2d580

File tree

13 files changed

+123
-28
lines changed

13 files changed

+123
-28
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ public final class io/sentry/android/core/ActivityFramesTracker {
33
public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;)V
44
public fun addActivity (Landroid/app/Activity;)V
55
public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V
6+
public fun setMetrics (Lio/sentry/protocol/SentryId;)V
67
public fun stop ()V
78
public fun takeMetrics (Lio/sentry/protocol/SentryId;)Ljava/util/Map;
89
}
@@ -199,7 +200,7 @@ public final class io/sentry/android/core/TempSensorBreadcrumbsIntegration : and
199200
}
200201

201202
public final class io/sentry/android/core/UserInteractionIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable {
202-
public fun <init> (Landroid/app/Application;Lio/sentry/android/core/LoadClass;)V
203+
public fun <init> (Landroid/app/Application;Lio/sentry/android/core/LoadClass;Lio/sentry/android/core/ActivityFramesTracker;)V
203204
public fun close ()V
204205
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
205206
public fun onActivityDestroyed (Landroid/app/Activity;)V

sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ public synchronized void addActivity(final @NotNull Activity activity) {
5555
frameMetricsAggregator.add(activity);
5656
}
5757

58+
public synchronized void setMetrics(final @NotNull SentryId sentryId) {
59+
setMetrics(null, sentryId);
60+
}
61+
5862
@SuppressWarnings("NullAway")
5963
public synchronized void setMetrics(
60-
final @NotNull Activity activity, final @NotNull SentryId sentryId) {
64+
final @Nullable Activity activity, final @NotNull SentryId sentryId) {
6165
if (!isFrameMetricsAggregatorAvailable()) {
6266
return;
6367
}
@@ -68,7 +72,11 @@ public synchronized void setMetrics(
6872

6973
SparseIntArray[] framesRates = null;
7074
try {
71-
framesRates = frameMetricsAggregator.remove(activity);
75+
if (activity != null) {
76+
framesRates = frameMetricsAggregator.remove(activity);
77+
} else {
78+
framesRates = frameMetricsAggregator.getMetrics();
79+
}
7280
} catch (Throwable ignored) {
7381
// throws IllegalArgumentException when attempting to remove OnFrameMetricsAvailableListener
7482
// that was never added.

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ private static void installDefaultIntegrations(
194194
options.addIntegration(
195195
new ActivityLifecycleIntegration(
196196
(Application) context, buildInfoProvider, activityFramesTracker));
197-
options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
197+
options.addIntegration(
198+
new UserInteractionIntegration((Application) context, loadClass, activityFramesTracker));
198199
if (isFragmentAvailable) {
199200
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
200201
}

sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_COLD;
44
import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_WARM;
55
import static io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP;
6+
import static io.sentry.android.core.internal.gestures.SentryGestureListener.UI_ACTION;
67

78
import io.sentry.EventProcessor;
89
import io.sentry.SentryEvent;
@@ -82,7 +83,8 @@ public SentryEvent process(@NotNull SentryEvent event, @Nullable Map<String, Obj
8283
// users it, we'll also add the metrics if available
8384
if (eventId != null
8485
&& spanContext != null
85-
&& spanContext.getOperation().contentEquals(UI_LOAD_OP)) {
86+
&& (spanContext.getOperation().contentEquals(UI_LOAD_OP)
87+
|| spanContext.getOperation().startsWith(UI_ACTION))) {
8688
final Map<String, @NotNull MeasurementValue> framesMetrics =
8789
activityFramesTracker.takeMetrics(eventId);
8890
if (framesMetrics != null) {

sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ public final class UserInteractionIntegration
2727
private final boolean isAndroidXAvailable;
2828
private final boolean isAndroidXScrollViewAvailable;
2929

30+
private final @NotNull ActivityFramesTracker activityFramesTracker;
31+
3032
public UserInteractionIntegration(
31-
final @NotNull Application application, final @NotNull LoadClass classLoader) {
33+
final @NotNull Application application,
34+
final @NotNull LoadClass classLoader,
35+
final @NotNull ActivityFramesTracker activityFramesTracker) {
3236
this.application = Objects.requireNonNull(application, "Application is required");
37+
this.activityFramesTracker =
38+
Objects.requireNonNull(activityFramesTracker, "ActivityFramesTracker is required");
3339

3440
isAndroidXAvailable =
3541
classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options);
@@ -53,7 +59,8 @@ private void startTracking(final @NotNull Activity activity) {
5359
}
5460

5561
final SentryGestureListener gestureListener =
56-
new SentryGestureListener(activity, hub, options, isAndroidXScrollViewAvailable);
62+
new SentryGestureListener(
63+
activity, hub, options, activityFramesTracker, isAndroidXScrollViewAvailable);
5764
window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options));
5865
}
5966
}
@@ -82,7 +89,14 @@ private void stopTracking(final @NotNull Activity activity) {
8289
public void onActivityCreated(@NotNull Activity activity, @Nullable Bundle bundle) {}
8390

8491
@Override
85-
public void onActivityStarted(@NotNull Activity activity) {}
92+
public void onActivityStarted(@NotNull Activity activity) {
93+
// The docs on the screen rendering performance tracing
94+
// (https://firebase.google.com/docs/perf-mon/screen-traces?platform=android#definition),
95+
// state that the tracing starts for every Activity class when the app calls .onActivityStarted.
96+
// Adding an Activity in onActivityCreated leads to Window.FEATURE_NO_TITLE not
97+
// working. Moving this to onActivityStarted fixes the problem.
98+
activityFramesTracker.addActivity(activity);
99+
}
86100

87101
@Override
88102
public void onActivityResumed(@NotNull Activity activity) {
@@ -140,5 +154,7 @@ public void close() throws IOException {
140154
if (options != null) {
141155
options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration removed.");
142156
}
157+
158+
activityFramesTracker.stop();
143159
}
144160
}

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.Scope;
1616
import io.sentry.SentryLevel;
1717
import io.sentry.SpanStatus;
18+
import io.sentry.android.core.ActivityFramesTracker;
1819
import io.sentry.android.core.SentryAndroidOptions;
1920
import java.lang.ref.WeakReference;
2021
import java.util.Collections;
@@ -28,12 +29,13 @@
2829
@ApiStatus.Internal
2930
public final class SentryGestureListener implements GestureDetector.OnGestureListener {
3031

31-
static final String UI_ACTION = "ui.action";
32+
public static final String UI_ACTION = "ui.action";
3233

3334
private final @NotNull WeakReference<Activity> activityRef;
3435
private final @NotNull IHub hub;
3536
private final @NotNull SentryAndroidOptions options;
3637
private final boolean isAndroidXAvailable;
38+
private final @NotNull ActivityFramesTracker activityFramesTracker;
3739

3840
private @NotNull WeakReference<View> activeView = new WeakReference<>(null);
3941
private @Nullable ITransaction activeTransaction = null;
@@ -45,10 +47,12 @@ public SentryGestureListener(
4547
final @NotNull Activity currentActivity,
4648
final @NotNull IHub hub,
4749
final @NotNull SentryAndroidOptions options,
50+
final @NotNull ActivityFramesTracker activityFramesTracker,
4851
final boolean isAndroidXAvailable) {
4952
this.activityRef = new WeakReference<>(currentActivity);
5053
this.hub = hub;
5154
this.options = options;
55+
this.activityFramesTracker = activityFramesTracker;
5256
this.isAndroidXAvailable = isAndroidXAvailable;
5357
}
5458

@@ -250,7 +254,15 @@ private void startTracing(final @NotNull View target, final @NotNull String even
250254
final String name = getActivityName(activity) + "." + viewId;
251255
final String op = UI_ACTION + "." + eventType;
252256
final ITransaction transaction =
253-
hub.startTransaction(name, op, true, options.getIdleTimeout(), true);
257+
hub.startTransaction(
258+
name,
259+
op,
260+
true,
261+
options.getIdleTimeout(),
262+
true,
263+
(finishingTransaction) -> {
264+
activityFramesTracker.setMetrics(finishingTransaction.getEventId());
265+
});
254266

255267
hub.configureScope(
256268
scope -> {

sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,25 @@ class UserInteractionIntegrationTest {
3333
val activity = mock<Activity>()
3434
val window = mock<Window>()
3535
val loadClass = mock<LoadClass>()
36+
val activityFramesTracker = mock<ActivityFramesTracker>()
3637

3738
fun getSut(
3839
callback: Window.Callback? = null,
3940
isAndroidXAvailable: Boolean = true
4041
): UserInteractionIntegration {
41-
whenever(loadClass.isClassAvailable(any(), anyOrNull<SentryAndroidOptions>())).thenReturn(isAndroidXAvailable)
42+
whenever(
43+
loadClass.isClassAvailable(
44+
any(),
45+
anyOrNull<SentryAndroidOptions>()
46+
)
47+
).thenReturn(isAndroidXAvailable)
4248
whenever(hub.options).thenReturn(options)
4349
whenever(window.callback).thenReturn(callback)
4450
whenever(activity.window).thenReturn(window)
4551

4652
val resources = mockResources()
4753
whenever(activity.resources).thenReturn(resources)
48-
return UserInteractionIntegration(application, loadClass)
54+
return UserInteractionIntegration(application, loadClass, activityFramesTracker)
4955
}
5056

5157
companion object {
@@ -90,6 +96,16 @@ class UserInteractionIntegrationTest {
9096
verify(fixture.application).unregisterActivityLifecycleCallbacks(any())
9197
}
9298

99+
@Test
100+
fun `when UserInteractionIntegration is closed stops ActivityFramesTracker`() {
101+
val sut = fixture.getSut()
102+
sut.register(fixture.hub, fixture.options)
103+
104+
sut.close()
105+
106+
verify(fixture.activityFramesTracker).stop()
107+
}
108+
93109
@Test
94110
fun `when androidx is unavailable doesn't register a callback`() {
95111
val sut = fixture.getSut(isAndroidXAvailable = false)
@@ -99,6 +115,16 @@ class UserInteractionIntegrationTest {
99115
verify(fixture.application, never()).registerActivityLifecycleCallbacks(any())
100116
}
101117

118+
@Test
119+
fun `adds activity to ActivityFramesTracker on activity started`() {
120+
val sut = fixture.getSut()
121+
sut.register(fixture.hub, fixture.options)
122+
123+
sut.onActivityStarted(fixture.activity)
124+
125+
verify(fixture.activityFramesTracker).addActivity(fixture.activity)
126+
}
127+
102128
@Test
103129
fun `registers window callback on activity resumed`() {
104130
val sut = fixture.getSut()

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class SentryGestureListenerClickTest {
7777
activity,
7878
hub,
7979
options,
80+
mock(),
8081
true
8182
)
8283
}

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SentryGestureListenerScrollTest {
7070
activity,
7171
hub,
7272
options,
73+
mock(),
7374
isAndroidXAvailable
7475
)
7576
}

0 commit comments

Comments
 (0)