diff --git a/CHANGELOG.md b/CHANGELOG.md index fc634f92b4..6dc2e2964c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Fixes + +- Use sentry-java `getFramesDelay` API instead of custom frame delay collector ([#6074](https://github.com/getsentry/sentry-react-native/pull/6074)) + ## 8.10.0 ### Features diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryFrameDelayCollector.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryFrameDelayCollector.java deleted file mode 100644 index a3295ed4b4..0000000000 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryFrameDelayCollector.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.sentry.react; - -import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import org.jetbrains.annotations.Nullable; - -/** - * Collects per-frame delay data from {@link SentryFrameMetricsCollector} and provides a method to - * query the accumulated delay within a given time range. - * - *
This is a temporary solution until sentry-java exposes a queryable API for frames delay
- * (similar to sentry-cocoa's getFramesDelaySPI).
- */
-public class RNSentryFrameDelayCollector
- implements SentryFrameMetricsCollector.FrameMetricsCollectorListener {
-
- private static final long MAX_FRAME_AGE_NANOS = 5L * 60 * 1_000_000_000L; // 5 minutes
-
- private final List Handles partial overlap: if a frame's delay period partially falls within the query range,
- * only the overlapping portion is counted.
- *
- * @param startNanos start of the query range in system nanos (e.g., System.nanoTime())
- * @param endNanos end of the query range in system nanos
- * @return delay in seconds, or -1 if no data is available
- */
- public double getFramesDelay(long startNanos, long endNanos) {
- if (startNanos >= endNanos) {
- return -1;
- }
-
- long totalDelayNanos = 0;
-
- for (FrameRecord frame : frames) {
- if (frame.endNanos <= startNanos) {
- continue;
- }
- if (frame.startNanos >= endNanos) {
- break;
- }
-
- // The delay portion of a frame is at the end of the frame duration.
- // delayStart = frameEnd - delay, delayEnd = frameEnd
- long delayStart = frame.endNanos - frame.delayNanos;
- long delayEnd = frame.endNanos;
-
- // Intersect the delay interval with the query range
- long overlapStart = Math.max(delayStart, startNanos);
- long overlapEnd = Math.min(delayEnd, endNanos);
-
- if (overlapEnd > overlapStart) {
- totalDelayNanos += (overlapEnd - overlapStart);
- }
- }
-
- return totalDelayNanos / 1e9;
- }
-
- private void pruneOldFrames(long currentNanos) {
- long cutoff = currentNanos - MAX_FRAME_AGE_NANOS;
- // Remove from the front one-by-one. CopyOnWriteArrayList.remove(0) is O(n) per call,
- // but old frames are pruned incrementally so typically only 0-1 entries are removed.
- while (!frames.isEmpty() && frames.get(0).endNanos < cutoff) {
- frames.remove(0);
- }
- }
-
- private static class FrameRecord {
- final long startNanos;
- final long endNanos;
- final long delayNanos;
-
- FrameRecord(long startNanos, long endNanos, long delayNanos) {
- this.startNanos = startNanos;
- this.endNanos = endNanos;
- this.delayNanos = delayNanos;
- }
- }
-}
diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java
index 4136eb5d3b..c255439416 100644
--- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java
+++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java
@@ -48,6 +48,7 @@
import io.sentry.android.core.SentryShakeDetector;
import io.sentry.android.core.ViewHierarchyEventProcessor;
import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader;
+import io.sentry.android.core.SentryFramesDelayResult;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.protocol.Geo;
@@ -98,7 +99,8 @@ public class RNSentryModuleImpl {
private final ReactApplicationContext reactApplicationContext;
private final PackageInfo packageInfo;
private FrameMetricsAggregator frameMetricsAggregator = null;
- private final RNSentryFrameDelayCollector frameDelayCollector = new RNSentryFrameDelayCollector();
+ private @Nullable SentryFrameMetricsCollector frameMetricsCollector = null;
+ private @Nullable String frameMetricsListenerId = null;
private boolean androidXAvailable;
@VisibleForTesting static long lastStartTimestampMs = -1;
@@ -413,9 +415,15 @@ public void fetchNativeFramesDelay(
long startNanos = nowNanos - (long) (startOffsetSeconds * 1e9);
long endNanos = nowNanos - (long) (endOffsetSeconds * 1e9);
- double delaySeconds = frameDelayCollector.getFramesDelay(startNanos, endNanos);
- if (delaySeconds >= 0) {
- promise.resolve(delaySeconds);
+ if (frameMetricsCollector == null) {
+ promise.resolve(null);
+ return;
+ }
+
+ SentryFramesDelayResult result =
+ frameMetricsCollector.getFramesDelay(startNanos, endNanos);
+ if (result.getDelaySeconds() >= 0) {
+ promise.resolve(result.getDelaySeconds());
} else {
promise.resolve(null);
}
@@ -747,12 +755,22 @@ public void enableNativeFramesTracking() {
if (options instanceof SentryAndroidOptions) {
final SentryFrameMetricsCollector collector =
((SentryAndroidOptions) options).getFrameMetricsCollector();
- if (frameDelayCollector.start(collector)) {
- logger.log(SentryLevel.INFO, "RNSentryFrameDelayCollector installed.");
+ if (collector != null) {
+ // Register a no-op listener to ensure frame metrics collection is active.
+ // This is needed so that getFramesDelay() has data to query.
+ stopFrameMetricsCollection();
+ frameMetricsCollector = collector;
+ frameMetricsListenerId =
+ collector.startCollection(
+ (startNanos, endNanos, durationNanos, delayNanos, isSlow, isFrozen, refreshRate)
+ -> {});
+ if (frameMetricsListenerId != null) {
+ logger.log(SentryLevel.INFO, "SentryFrameMetricsCollector listener installed.");
+ }
}
}
} catch (Throwable ignored) { // NOPMD - We don't want to crash in any case
- logger.log(SentryLevel.WARNING, "Error starting RNSentryFrameDelayCollector.");
+ logger.log(SentryLevel.WARNING, "Error starting frame metrics collection.");
}
}
@@ -761,7 +779,15 @@ public void disableNativeFramesTracking() {
frameMetricsAggregator.stop();
frameMetricsAggregator = null;
}
- frameDelayCollector.stop();
+ stopFrameMetricsCollection();
+ }
+
+ private void stopFrameMetricsCollection() {
+ if (frameMetricsCollector != null && frameMetricsListenerId != null) {
+ frameMetricsCollector.stopCollection(frameMetricsListenerId);
+ }
+ frameMetricsCollector = null;
+ frameMetricsListenerId = null;
}
public void getNewScreenTimeToDisplay(Promise promise) {