-
-
Notifications
You must be signed in to change notification settings - Fork 435
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Collect slow and frozen frames for spans (#3111)
* Collect slow and frozen frames using SentryFrameMetricsCollector and SpanFrameMetricsCollector * Rename fast frame to normal frame, remove fastFrameDuration * Extend performance collector APIs * Update changelog * Collect slow and frozen frames using SentryFrameMetricsCollector and SpanFrameMetricsCollector * Rename fast frame to normal frame, remove fastFrameDuration * Fix PR feedback * Fix nullability and remove unused field * Rename FrameMetrics to SentryFrameMetrics * Collect delay instead of total duration * Fix tests * Revert "Fix tests" This reverts commit 5b9c3c6. * Properly fix tests
- Loading branch information
Showing
13 changed files
with
611 additions
and
42 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
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
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
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
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
109 changes: 109 additions & 0 deletions
109
sentry-android-core/src/main/java/io/sentry/android/core/SentryFrameMetrics.java
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,109 @@ | ||
package io.sentry.android.core; | ||
|
||
import org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
@ApiStatus.Internal | ||
final class SentryFrameMetrics { | ||
|
||
private int normalFrameCount; | ||
private int slowFrameCount; | ||
private int frozenFrameCount; | ||
|
||
private long slowFrameDelayNanos; | ||
private long frozenFrameDelayNanos; | ||
|
||
public SentryFrameMetrics() {} | ||
|
||
public SentryFrameMetrics( | ||
int normalFrameCount, | ||
int slowFrameCount, | ||
long slowFrameDelayNanos, | ||
int frozenFrameCount, | ||
long frozenFrameDelayNanos) { | ||
this.normalFrameCount = normalFrameCount; | ||
|
||
this.slowFrameCount = slowFrameCount; | ||
this.slowFrameDelayNanos = slowFrameDelayNanos; | ||
|
||
this.frozenFrameCount = frozenFrameCount; | ||
this.frozenFrameDelayNanos = frozenFrameDelayNanos; | ||
} | ||
|
||
public void addSlowFrame(final long delayNanos) { | ||
slowFrameDelayNanos += delayNanos; | ||
slowFrameCount++; | ||
} | ||
|
||
public void addFrozenFrame(final long delayNanos) { | ||
frozenFrameDelayNanos += delayNanos; | ||
frozenFrameCount++; | ||
} | ||
|
||
public void addNormalFrame() { | ||
normalFrameCount++; | ||
} | ||
|
||
public int getNormalFrameCount() { | ||
return normalFrameCount; | ||
} | ||
|
||
public int getSlowFrameCount() { | ||
return slowFrameCount; | ||
} | ||
|
||
public int getFrozenFrameCount() { | ||
return frozenFrameCount; | ||
} | ||
|
||
public long getSlowFrameDelayNanos() { | ||
return slowFrameDelayNanos; | ||
} | ||
|
||
public long getFrozenFrameDelayNanos() { | ||
return frozenFrameDelayNanos; | ||
} | ||
|
||
public int getTotalFrameCount() { | ||
return normalFrameCount + slowFrameCount + frozenFrameCount; | ||
} | ||
|
||
public void clear() { | ||
normalFrameCount = 0; | ||
|
||
slowFrameCount = 0; | ||
slowFrameDelayNanos = 0; | ||
|
||
frozenFrameCount = 0; | ||
frozenFrameDelayNanos = 0; | ||
} | ||
|
||
@NotNull | ||
public SentryFrameMetrics duplicate() { | ||
return new SentryFrameMetrics( | ||
normalFrameCount, | ||
slowFrameCount, | ||
slowFrameDelayNanos, | ||
frozenFrameCount, | ||
frozenFrameDelayNanos); | ||
} | ||
|
||
/** | ||
* @param other the other frame metrics to compare to, usually the older one | ||
* @return the difference between two frame metrics (this minus other) | ||
*/ | ||
@NotNull | ||
public SentryFrameMetrics diffTo(final @NotNull SentryFrameMetrics other) { | ||
return new SentryFrameMetrics( | ||
normalFrameCount - other.normalFrameCount, | ||
slowFrameCount - other.slowFrameCount, | ||
slowFrameDelayNanos - other.slowFrameDelayNanos, | ||
frozenFrameCount - other.frozenFrameCount, | ||
frozenFrameDelayNanos - other.frozenFrameDelayNanos); | ||
} | ||
|
||
public boolean containsValidData() { | ||
// TODO sanity check durations? | ||
return getTotalFrameCount() > 0; | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java
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,124 @@ | ||
package io.sentry.android.core; | ||
|
||
import io.sentry.IPerformanceContinuousCollector; | ||
import io.sentry.ISpan; | ||
import io.sentry.NoOpSpan; | ||
import io.sentry.NoOpTransaction; | ||
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 org.jetbrains.annotations.ApiStatus; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
@ApiStatus.Internal | ||
public class SpanFrameMetricsCollector | ||
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, SentryFrameMetrics> metricsAtSpanStart; | ||
|
||
private @NotNull final SentryFrameMetrics currentFrameMetrics; | ||
private final boolean enabled; | ||
|
||
public SpanFrameMetricsCollector(final @NotNull SentryAndroidOptions options) { | ||
frameMetricsCollector = options.getFrameMetricsCollector(); | ||
enabled = options.isEnablePerformanceV2() && options.isEnableFramesTracking(); | ||
|
||
metricsAtSpanStart = new HashMap<>(); | ||
currentFrameMetrics = new SentryFrameMetrics(); | ||
} | ||
|
||
@Override | ||
public void onSpanStarted(final @NotNull ISpan span) { | ||
if (!enabled) { | ||
return; | ||
} | ||
if (span instanceof NoOpSpan) { | ||
return; | ||
} | ||
if (span instanceof NoOpTransaction) { | ||
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 (!enabled) { | ||
return; | ||
} | ||
if (span instanceof NoOpSpan) { | ||
return; | ||
} | ||
if (span instanceof NoOpTransaction) { | ||
return; | ||
} | ||
|
||
@Nullable SentryFrameMetrics diff = null; | ||
synchronized (lock) { | ||
final @Nullable SentryFrameMetrics metricsAtStart = | ||
metricsAtSpanStart.remove(span.getSpanContext().getSpanId()); | ||
if (metricsAtStart != null) { | ||
diff = currentFrameMetrics.diffTo(metricsAtStart); | ||
} | ||
} | ||
if (diff != null && diff.containsValidData()) { | ||
span.setData(SpanDataConvention.FRAMES_SLOW, diff.getSlowFrameCount()); | ||
span.setData(SpanDataConvention.FRAMES_FROZEN, diff.getFrozenFrameCount()); | ||
span.setData(SpanDataConvention.FRAMES_TOTAL, diff.getTotalFrameCount()); | ||
} | ||
|
||
synchronized (lock) { | ||
if (metricsAtSpanStart.isEmpty()) { | ||
clear(); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void clear() { | ||
synchronized (lock) { | ||
if (listenerId != null) { | ||
if (frameMetricsCollector != null) { | ||
frameMetricsCollector.stopCollection(listenerId); | ||
} | ||
listenerId = null; | ||
} | ||
metricsAtSpanStart.clear(); | ||
currentFrameMetrics.clear(); | ||
} | ||
} | ||
|
||
@Override | ||
public void onFrameMetricCollected( | ||
final long frameStartNanos, | ||
final long frameEndNanos, | ||
final long durationNanos, | ||
final long delayNanos, | ||
final boolean isSlow, | ||
final boolean isFrozen, | ||
final float refreshRate) { | ||
|
||
if (isFrozen) { | ||
currentFrameMetrics.addFrozenFrame(delayNanos); | ||
} else if (isSlow) { | ||
currentFrameMetrics.addSlowFrame(delayNanos); | ||
} else { | ||
currentFrameMetrics.addNormalFrame(); | ||
} | ||
} | ||
} |
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
Oops, something went wrong.