Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow and frozen frames + frame delay for Spans #3081

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d59ca3f
Attach app-start spans
markushi Nov 21, 2023
4232431
Merge branch 'main' into feat/app-start-spans
markushi Nov 24, 2023
69aca74
Update Changelog
markushi Nov 24, 2023
e856b81
Fix tests
markushi Nov 24, 2023
aea2a36
Implement PR feedback
markushi Nov 28, 2023
fe3586d
Merge branch 'main' into feat/app-start-spans
markushi Nov 30, 2023
80ec3f2
Fix merge
markushi Nov 30, 2023
1a0fcee
Address PR feedback, improve tests
markushi Nov 30, 2023
dd32cf3
Merge branch 'main' into feat/app-start-spans
markushi Nov 30, 2023
1141aed
Update Changelog
markushi Nov 30, 2023
1cdf4e5
Expose SentryFrameMetricsCollector via options, calculate frame delay
markushi Dec 1, 2023
7f60bc7
Added basic plumbing for using SentryFrameMetricsCollector for slow/f…
markushi Dec 4, 2023
3dfc9b4
Add span hooks, fix typos
markushi Dec 4, 2023
1a66cce
Implement PR feedback
markushi Dec 5, 2023
d43311a
Rename starfish to performance-v2
markushi Dec 5, 2023
3257c66
Address PR feedback
markushi Dec 6, 2023
a93402a
Address PR feedback
markushi Dec 6, 2023
ee245e7
Merge branch 'main' into feat/app-start-spans
markushi Dec 11, 2023
b8a1b57
[Starfish] Attach app start spans to app.start.cold txn (#3067)
markushi Dec 12, 2023
cadcad4
Fix tests
markushi Dec 12, 2023
6cbf39a
Merge branch 'feat/app-start-spans' into feat/slow-frozen-frames
markushi Dec 12, 2023
383e5d7
Merge branch 'main' into feat/app-start-spans
markushi Dec 12, 2023
fb0e5e7
Improve docs and changelog
markushi Dec 12, 2023
9f1d8fb
Merge branch 'feat/app-start-spans' into feat/slow-frozen-frames
markushi Dec 12, 2023
4a76616
Allow span data to be written, even after span was finished
markushi Dec 14, 2023
6dc9094
Fixes
markushi Dec 14, 2023
ac11be2
Unify span metric calcuations
markushi Dec 14, 2023
5825e8a
temp
markushi Dec 15, 2023
bb09cdf
Merge branch 'main' into feat/slow-frozen-frames
markushi Dec 15, 2023
69c25a1
Format code
getsentry-bot Dec 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/AndroidCpuCollector : io/sentry/ICollector {
public final class io/sentry/android/core/AndroidCpuCollector : io/sentry/IPerformanceSnapshotCollector {
public fun <init> (Lio/sentry/ILogger;Lio/sentry/android/core/BuildInfoProvider;)V
public fun collect (Lio/sentry/PerformanceCollectionData;)V
public fun setup ()V
Expand All @@ -43,7 +43,7 @@ public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger {
public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
}

public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/ICollector {
public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IPerformanceSnapshotCollector {
public fun <init> ()V
public fun collect (Lio/sentry/PerformanceCollectionData;)V
public fun setup ()V
Expand Down Expand Up @@ -71,6 +71,14 @@ public class io/sentry/android/core/AndroidProfiler$ProfileStartData {
public fun <init> (JJ)V
}

public class io/sentry/android/core/AndroidSlowFrozenFrameCollector : io/sentry/IPerformanceContinuousCollector, io/sentry/android/core/internal/util/SentryFrameMetricsCollector$FrameMetricsCollectorListener {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun clear ()V
public fun onFrameMetricCollected (JJJF)V
public fun onSpanFinished (Lio/sentry/ISpan;)V
public fun onSpanStarted (Lio/sentry/ISpan;)V
}

public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
Expand Down Expand Up @@ -246,6 +254,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun getBeforeScreenshotCaptureCallback ()Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;
public fun getBeforeViewHierarchyCaptureCallback ()Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;
public fun getDebugImagesLoader ()Lio/sentry/android/core/IDebugImagesLoader;
public fun getFrameMetricsCollector ()Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;
public fun getNativeSdkName ()Ljava/lang/String;
public fun getProfilingTracesHz ()I
public fun getProfilingTracesIntervalMillis ()I
Expand Down Expand Up @@ -291,6 +300,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableRootCheck (Z)V
public fun setEnableScopeSync (Z)V
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setFrameMetricsCollector (Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V
public fun setNativeSdkName (Ljava/lang/String;)V
public fun setProfilingTracesHz (I)V
public fun setProfilingTracesIntervalMillis (I)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
* A class that tracks slow and frozen frames using the FrameMetricsAggregator class from
* androidx.core package. It also checks if the FrameMetricsAggregator class is available at
* runtime.
*
* <p>If starfish is enabled, frame metrics are recorded using {@link
* io.sentry.android.core.internal.util.SentryFrameMetricsCollector} via {@link
* AndroidSlowFrozenFrameCollector} instead.
*/
public final class ActivityFramesTracker {

Expand All @@ -42,7 +46,8 @@ public ActivityFramesTracker(
final boolean androidXAvailable =
loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", options.getLogger());

if (androidXAvailable) {
/** */
if (androidXAvailable && !options.isEnablePerformanceV2()) {
frameMetricsAggregator = new FrameMetricsAggregator();
}
this.options = options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import android.system.Os;
import android.system.OsConstants;
import io.sentry.CpuCollectionData;
import io.sentry.ICollector;
import io.sentry.ILogger;
import io.sentry.IPerformanceSnapshotCollector;
import io.sentry.PerformanceCollectionData;
import io.sentry.SentryLevel;
import io.sentry.util.FileUtils;
Expand All @@ -22,7 +22,7 @@
// The content of the /proc/self/stat file is specified in
// https://man7.org/linux/man-pages/man5/proc.5.html
@ApiStatus.Internal
public final class AndroidCpuCollector implements ICollector {
public final class AndroidCpuCollector implements IPerformanceSnapshotCollector {

private long lastRealtimeNanos = 0;
private long lastCpuNanos = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package io.sentry.android.core;

import android.os.Debug;
import io.sentry.ICollector;
import io.sentry.IPerformanceSnapshotCollector;
import io.sentry.MemoryCollectionData;
import io.sentry.PerformanceCollectionData;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public class AndroidMemoryCollector implements ICollector {
public class AndroidMemoryCollector implements IPerformanceSnapshotCollector {

@Override
public void setup() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ static void loadDefaultAndMetadataOptions(
// set a lower flush timeout on Android to avoid ANRs
options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS);

options.setFrameMetricsCollector(
new SentryFrameMetricsCollector(context, logger, buildInfoProvider));

ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
initializeCacheDirs(context, options);

Expand Down Expand Up @@ -145,10 +148,8 @@ static void initializeIntegrationsAndProcessors(
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
options.setTransportGate(new AndroidTransportGate(options));
final SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));
new AndroidTransactionProfiler(context, options, buildInfoProvider));
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));

Expand Down Expand Up @@ -183,9 +184,13 @@ static void initializeIntegrationsAndProcessors(
}

options.setMainThreadChecker(AndroidMainThreadChecker.getInstance());
if (options.getCollectors().isEmpty()) {
options.addCollector(new AndroidMemoryCollector());
options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
if (options.getPerformanceCollectors().isEmpty()) {
options.addPerformanceCollector(new AndroidMemoryCollector());
options.addPerformanceCollector(
new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
}
if (options.isEnablePerformanceV2()) {
options.addPerformanceCollector(new AndroidSlowFrozenFrameCollector(options));
}
options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.sentry.android.core;

import android.util.Log;
import io.sentry.FrameMetrics;
import io.sentry.IPerformanceContinuousCollector;
import io.sentry.ISpan;
import io.sentry.NoOpSpan;
import io.sentry.SpanDataConvention;
import io.sentry.SpanId;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class AndroidSlowFrozenFrameCollector
implements IPerformanceContinuousCollector,
SentryFrameMetricsCollector.FrameMetricsCollectorListener {
private @NotNull final Object lock = new Object();
private @Nullable final SentryFrameMetricsCollector frameMetricsCollector;
private @Nullable volatile String listenerId;
private @NotNull final Map<SpanId, FrameMetrics> metricsAtSpanStart;

private @NotNull final FrameMetrics currentFrameMetrics;
final long nanosInSecond = TimeUnit.SECONDS.toNanos(1);
final long frozenFrameThresholdNanos = TimeUnit.MILLISECONDS.toNanos(700);
float lastRefreshRate = 0;

public AndroidSlowFrozenFrameCollector(@NotNull SentryAndroidOptions options) {
frameMetricsCollector = options.getFrameMetricsCollector();
metricsAtSpanStart = new HashMap<>();
currentFrameMetrics = new FrameMetrics();
}

@Override
public void onSpanStarted(final @NotNull ISpan span) {
if (span instanceof NoOpSpan) {
return;
}
synchronized (lock) {
metricsAtSpanStart.put(span.getSpanContext().getSpanId(), currentFrameMetrics.duplicate());

if (listenerId == null) {
if (frameMetricsCollector != null) {
listenerId = frameMetricsCollector.startCollection(this);
}
}
}
}

@Override
public void onSpanFinished(final @NotNull ISpan span) {
if (span instanceof NoOpSpan) {
return;
}
@Nullable FrameMetrics diff = null;
synchronized (lock) {
final @Nullable FrameMetrics metricsAtStart =
metricsAtSpanStart.remove(span.getSpanContext().getSpanId());
if (metricsAtStart != null) {
diff = currentFrameMetrics.diffTo(metricsAtStart);
}
}
if (diff != null) {
final int totalFrameCount = diff.getTotalFrameCount();
if (totalFrameCount > 0) {
span.setData(SpanDataConvention.FRAMES_SLOW, diff.getSlowFrameCount());
span.setData(SpanDataConvention.FRAMES_FROZEN, diff.getFrozenFrameCount());
span.setData(SpanDataConvention.FRAMES_TOTAL, diff.getFastFrameCount());
}
}
}

@Override
public void clear() {
Log.d("TAG", "clear");
synchronized (lock) {
if (listenerId != null) {
if (frameMetricsCollector != null) {
frameMetricsCollector.stopCollection(listenerId);
}
listenerId = null;
}
metricsAtSpanStart.clear();
currentFrameMetrics.clear();
}
}

@Override
public void onFrameMetricCollected(
long frameStartNanos,
long frameEndNanos,
long durationNanos,
long delayNanos,
boolean isSlow,
boolean isFrozen,
float refreshRate) {

lastRefreshRate = (int) (refreshRate * 100) / 100F;

if (isFrozen) {
currentFrameMetrics.addFrozenFrame(durationNanos);
} else if (isSlow) {
currentFrameMetrics.addSlowFrame(durationNanos);
} else {
currentFrameMetrics.addFastFrame(durationNanos);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
final class AndroidTransactionProfiler implements ITransactionProfiler {
private final @NotNull Context context;
private final @NotNull SentryAndroidOptions options;
private final @NotNull IHub hub;
private final @NotNull BuildInfoProvider buildInfoProvider;
private boolean isInitialized = false;
private int transactionsCounter = 0;
Expand All @@ -44,27 +43,34 @@ final class AndroidTransactionProfiler implements ITransactionProfiler {
public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector) {
final @NotNull BuildInfoProvider buildInfoProvider) {
this(
context,
sentryAndroidOptions,
buildInfoProvider,
frameMetricsCollector,
Objects.requireNonNull(
sentryAndroidOptions.getFrameMetricsCollector(),
"sentryAndroidOptions.getFrameMetricsCollector() cannot be null."),
HubAdapter.getInstance());
}

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider) {
this(context, sentryAndroidOptions, buildInfoProvider, HubAdapter.getInstance());
}

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull IHub hub) {
this.context = Objects.requireNonNull(context, "The application context is required");
this.options = Objects.requireNonNull(sentryAndroidOptions, "SentryAndroidOptions is required");
this.hub = Objects.requireNonNull(hub, "Hub is required");
this.frameMetricsCollector =
Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required");
Objects.requireNonNull(
options.getFrameMetricsCollector(), "SentryFrameMetricsCollector is required");
this.buildInfoProvider =
Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.sentry.SentryOptions;
import io.sentry.SpanStatus;
import io.sentry.android.core.internal.util.RootChecker;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.protocol.Mechanism;
import io.sentry.protocol.SdkVersion;
import org.jetbrains.annotations.ApiStatus;
Expand Down Expand Up @@ -214,6 +215,8 @@ public interface BeforeCaptureCallback {

private boolean enablePerformanceV2 = false;

private @Nullable SentryFrameMetricsCollector frameMetricsCollector;

public SentryAndroidOptions() {
setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
Expand Down Expand Up @@ -613,4 +616,15 @@ public boolean isEnablePerformanceV2() {
public void setEnablePerformanceV2(final boolean enablePerformanceV2) {
this.enablePerformanceV2 = enablePerformanceV2;
}

@ApiStatus.Internal
public @Nullable SentryFrameMetricsCollector getFrameMetricsCollector() {
return frameMetricsCollector;
}

@ApiStatus.Internal
public void setFrameMetricsCollector(
final @Nullable SentryFrameMetricsCollector frameMetricsCollector) {
this.frameMetricsCollector = frameMetricsCollector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -570,14 +570,14 @@ class AndroidOptionsInitializerTest {
fun `AndroidMemoryCollector is set to options`() {
fixture.initSut()

assertTrue { fixture.sentryOptions.collectors.any { it is AndroidMemoryCollector } }
assertTrue { fixture.sentryOptions.performanceCollectors.any { it is AndroidMemoryCollector } }
}

@Test
fun `AndroidCpuCollector is set to options`() {
fixture.initSut()

assertTrue { fixture.sentryOptions.collectors.any { it is AndroidCpuCollector } }
assertTrue { fixture.sentryOptions.performanceCollectors.any { it is AndroidCpuCollector } }
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class AndroidTransactionProfilerTest {
transaction1 = SentryTracer(TransactionContext("", ""), hub)
transaction2 = SentryTracer(TransactionContext("", ""), hub)
transaction3 = SentryTracer(TransactionContext("", ""), hub)
return AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector, hub)
return AndroidTransactionProfiler(context, options, buildInfoProvider, hub)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<!-- how to disable sentry -->
<!-- <meta-data android:name="io.sentry.enabled" android:value="false" /> -->

<meta-data android:name="io.sentry.performance-v2" android:value="true" />
<meta-data android:name="io.sentry.performance-v2.enable" android:value="true" />

</application>
</manifest>
Loading