Skip to content

Commit

Permalink
Merge branch 'main' into feat/slow-frozen-frames
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi committed Dec 15, 2023
2 parents ac11be2 + 5825e8a commit bb09cdf
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 97 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
- Attaches spans for Application, ContentProvider, and Activities to app-start timings
- Uses Process.startUptimeMillis to calculate app-start timings
- To enable this feature set `options.isEnablePerformanceV2 = true`
- Move slow+frozen frame calculation, as well as frame delay inside SentryFrameMetricsCollector ([#3100](https://github.com/getsentry/sentry-java/pull/3100))

### Fixes

- Send breadcrumbs and client error even without transactions ([#3087](https://github.com/getsentry/sentry-java/pull/3087))
- Send breadcrumbs and client error in `SentryOkHttpEventListener` even without transactions ([#3087](https://github.com/getsentry/sentry-java/pull/3087))
- Keep `io.sentry.exception.SentryHttpClientException` from obfuscation to display proper issue title on Sentry ([#3093](https://github.com/getsentry/sentry-java/pull/3093))

### Dependencies

Expand Down
7 changes: 7 additions & 0 deletions sentry-android-core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,10 @@
-keep class io.sentry.apollo3.SentryApollo3ClientException { <init>(...); }

##---------------End: proguard configuration for sentry-apollo-3 ----------

##---------------Begin: proguard configuration for sentry-okhttp ----------

# we don't want this class to be obfuscated, otherwise issue's titles are obfuscated as well.
-keepnames class io.sentry.exception.SentryHttpClientException

##---------------End: proguard configuration for sentry-okhttp ----------
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ static void loadDefaultAndMetadataOptions(
options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS);

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

ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
initializeCacheDirs(context, options);
Expand Down Expand Up @@ -148,8 +148,7 @@ static void initializeIntegrationsAndProcessors(
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
options.setTransportGate(new AndroidTransportGate(options));
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider));
options.setTransactionProfiler(new AndroidTransactionProfiler(context, options, buildInfoProvider));
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ public AndroidProfiler(

@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) {
final long frameStartNanos,
final long frameEndNanos,
final long durationNanos,
final long delayNanos,
final boolean isSlow,
final boolean isFrozen,
final float refreshRate) {
// transactionStartNanos is calculated through SystemClock.elapsedRealtimeNanos(),
// but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp
// relative to transactionStartNanos
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ final class AndroidTransactionProfiler implements ITransactionProfiler {
private long transactionStartNanos;
private long profileStartCpuMillis;

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider) {
this(
context,
sentryAndroidOptions,
buildInfoProvider,
Objects.requireNonNull(sentryAndroidOptions.getFrameMetricsCollector(),"sentryAndroidOptions.getFrameMetricsCollector() cannot be null."),
HubAdapter.getInstance());
}

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ public void setEnablePerformanceV2(final boolean enablePerformanceV2) {

@ApiStatus.Internal
public void setFrameMetricsCollector(
final @Nullable SentryFrameMetricsCollector frameMetricsCollector) {
final @Nullable SentryFrameMetricsCollector frameMetricsCollector) {
this.frameMetricsCollector = frameMetricsCollector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class SentryFrameMetricsCollector implements Application.ActivityLifecycleCallbacks {
private static final long nanosInSecond = TimeUnit.SECONDS.toNanos(1);
private static final long oneSecondInNanos = TimeUnit.SECONDS.toNanos(1);
private static final long frozenFrameThresholdNanos = TimeUnit.MILLISECONDS.toNanos(700);

private final @NotNull BuildInfoProvider buildInfoProvider;
Expand Down Expand Up @@ -139,8 +138,6 @@ public SentryFrameMetricsCollector(
SentryLevel.ERROR, "Unable to get the frame timestamp from the choreographer: ", e);
}



frameMetricsAvailableListener =
(window, frameMetrics, dropCountSinceLastInvocation) -> {
final long now = System.nanoTime();
Expand All @@ -149,14 +146,14 @@ public SentryFrameMetricsCollector(
? window.getContext().getDisplay().getRefreshRate()
: window.getWindowManager().getDefaultDisplay().getRefreshRate();

final long expectedFrameDuration = (long) (nanosInSecond / refreshRate);
final long totalFrameDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
final long expectedFrameDuration = (long) (oneSecondInNanos / refreshRate);

final long cpuDuration = getFrameCpuDuration(frameMetrics);

// if totalDurationNanos is smaller than expectedFrameTimeNanos,
// it means that the frame was drawn within it's time budget, thus 0 delay
final long delayNanos = Math.max(0, totalFrameDuration - expectedFrameDuration);
final long delayNanos = Math.max(0, cpuDuration - expectedFrameDuration);

final long cpuDuration = getFrameCpuDuration(frameMetrics);
long startTime = getFrameStartTimestamp(frameMetrics);
// If we couldn't get the timestamp through reflection, we use current time
if (startTime < 0) {
Expand All @@ -174,18 +171,18 @@ public SentryFrameMetricsCollector(
// Most frames take just a few nanoseconds longer than the optimal calculated
// duration.
// Therefore we subtract one, because otherwise almost all frames would be slow.
final boolean isSlow = cpuDuration > nanosInSecond / (refreshRate - 1);
final boolean isSlow = cpuDuration > oneSecondInNanos / (refreshRate - 1);
final boolean isFrozen = isSlow && cpuDuration > frozenFrameThresholdNanos;

for (FrameMetricsCollectorListener l : listenerMap.values()) {
l.onFrameMetricCollected(
startTime,
lastFrameEndNanos,
cpuDuration,
delayNanos,
isSlow,
isFrozen,
refreshRate);
startTime,
lastFrameEndNanos,
cpuDuration,
delayNanos,
isSlow,
isFrozen,
refreshRate);
}
};
}
Expand Down Expand Up @@ -222,6 +219,8 @@ private long getFrameStartTimestamp(final @NotNull FrameMetrics frameMetrics) {
*/
@RequiresApi(api = Build.VERSION_CODES.N)
private long getFrameCpuDuration(final @NotNull FrameMetrics frameMetrics) {
// Inspired by JankStats
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt;l=74-79;drc=1de6215c6bd9e887e3d94556e9ac55cfb7b8c797
return frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION)
+ frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION)
+ frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION)
Expand Down Expand Up @@ -328,13 +327,16 @@ public interface FrameMetricsCollectorListener {
/**
* Called when a frame is collected.
*
* @param frameStartNanos Start timestamp of a frame in nanoseconds relative to System.nanotime().
* @param frameStartNanos Start timestamp of a frame in nanoseconds relative to
* System.nanotime().
* @param frameEndNanos End timestamp of a frame in nanoseconds relative to System.nanotime().
* @param durationNanos Duration in nanoseconds of the time spent from the cpu on the main
* thread to create the frame.
* @param delayNanos the frame delay, in nanoseconds.
* @param isSlow True if the frame is considered slow, rendering taking longer than the refresh-rate based budget, false otherwise.
* @param isFrozen True if the frame is considered frozen, rendering taking longer than 700ms, false otherwise.
* @param isSlow True if the frame is considered slow, rendering taking longer than the
* refresh-rate based budget, false otherwise.
* @param isFrozen True if the frame is considered frozen, rendering taking longer than 700ms,
* false otherwise.
* @param refreshRate the last known refresh rate when the frame was rendered.
*/
void onFrameMetricCollected(
Expand Down
Loading

0 comments on commit bb09cdf

Please sign in to comment.