diff --git a/CHANGELOG.md b/CHANGELOG.md index e9264bf740..a19f01b158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## Unreleased +### Features + +- (Internal) Extract Android Profiler and Measurements for Hybrid SDKs ([#3016](https://github.com/getsentry/sentry-java/pull/3016)) +- (Internal) Remove SentryOptions dependency from AndroidProfiler ([#3051](https://github.com/getsentry/sentry-java/pull/3051)) +- (Internal) Add `readBytesFromFile` for use in Hybrid SDKs ([#3052](https://github.com/getsentry/sentry-java/pull/3052)) + ### Fixes - Fix SIGSEV, SIGABRT and SIGBUS crashes happening after/around the August Google Play System update, see [#2955](https://github.com/getsentry/sentry-java/issues/2955) for more details (fix provided by Native SDK bump) -- (Internal) Extract Android Profiler and Measurements for Hybrid SDKs ([#3016](https://github.com/getsentry/sentry-java/pull/3016)) - Ensure DSN uses http/https protocol ([#3044](https://github.com/getsentry/sentry-java/pull/3044)) -- (Internal) Add `readBytesFromFile` for use in Hybrid SDKs ([#3052](https://github.com/getsentry/sentry-java/pull/3052)) ### Dependencies diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 60f562c02c..d6253b3f02 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -50,7 +50,7 @@ public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/ICollecto } public class io/sentry/android/core/AndroidProfiler { - public fun (Ljava/lang/String;ILio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V + public fun (Ljava/lang/String;ILio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ISentryExecutorService;Lio/sentry/ILogger;Lio/sentry/android/core/BuildInfoProvider;)V public fun close ()V public fun endAndCollect (ZLjava/util/List;)Lio/sentry/android/core/AndroidProfiler$ProfileEndData; public fun start ()Lio/sentry/android/core/AndroidProfiler$ProfileStartData; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java index ae03673d8b..a4e097ec00 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java @@ -6,6 +6,8 @@ import android.os.Process; import android.os.SystemClock; import io.sentry.CpuCollectionData; +import io.sentry.ILogger; +import io.sentry.ISentryExecutorService; import io.sentry.MemoryCollectionData; import io.sentry.PerformanceCollectionData; import io.sentry.SentryLevel; @@ -86,20 +88,23 @@ public ProfileEndData( private final @NotNull ArrayDeque frozenFrameRenderMeasurements = new ArrayDeque<>(); private final @NotNull Map measurementsMap = new HashMap<>(); - private final @NotNull SentryAndroidOptions options; private final @NotNull BuildInfoProvider buildInfoProvider; + private final @NotNull ISentryExecutorService executorService; + private final @NotNull ILogger logger; private boolean isRunning = false; public AndroidProfiler( final @NotNull String tracesFilesDirPath, final int intervalUs, final @NotNull SentryFrameMetricsCollector frameMetricsCollector, - final @NotNull SentryAndroidOptions sentryAndroidOptions, + final @NotNull ISentryExecutorService executorService, + final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider) { this.traceFilesDir = new File(Objects.requireNonNull(tracesFilesDirPath, "TracesFilesDirPath is required")); this.intervalUs = intervalUs; - this.options = Objects.requireNonNull(sentryAndroidOptions, "SentryAndroidOptions is required"); + this.logger = Objects.requireNonNull(logger, "Logger is required"); + this.executorService = Objects.requireNonNull(executorService, "ExecutorService is required."); this.frameMetricsCollector = Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required"); this.buildInfoProvider = @@ -110,17 +115,13 @@ public AndroidProfiler( public synchronized @Nullable ProfileStartData start() { // intervalUs is 0 only if there was a problem in the init if (intervalUs == 0) { - options - .getLogger() - .log( - SentryLevel.WARNING, - "Disabling profiling because intervaUs is set to %d", - intervalUs); + logger.log( + SentryLevel.WARNING, "Disabling profiling because intervaUs is set to %d", intervalUs); return null; } if (isRunning) { - options.getLogger().log(SentryLevel.WARNING, "Profiling has already started..."); + logger.log(SentryLevel.WARNING, "Profiling has already started..."); return null; } @@ -182,18 +183,13 @@ public void onFrameMetricCollected( // We stop profiling after a timeout to avoid huge profiles to be sent try { scheduledFinish = - options - .getExecutorService() - .schedule( - () -> timedOutProfilingData = endAndCollect(true, null), - PROFILING_TIMEOUT_MILLIS); + executorService.schedule( + () -> timedOutProfilingData = endAndCollect(true, null), PROFILING_TIMEOUT_MILLIS); } catch (RejectedExecutionException e) { - options - .getLogger() - .log( - SentryLevel.ERROR, - "Failed to call the executor. Profiling will not be automatically finished. Did you call Sentry.close()?", - e); + logger.log( + SentryLevel.ERROR, + "Failed to call the executor. Profiling will not be automatically finished. Did you call Sentry.close()?", + e); } transactionStartNanos = SystemClock.elapsedRealtimeNanos(); @@ -211,7 +207,7 @@ public void onFrameMetricCollected( return new ProfileStartData(transactionStartNanos, profileStartCpuMillis); } catch (Throwable e) { endAndCollect(false, null); - options.getLogger().log(SentryLevel.ERROR, "Unable to start a profile: ", e); + logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e); isRunning = false; return null; } @@ -227,7 +223,7 @@ public void onFrameMetricCollected( } if (!isRunning) { - options.getLogger().log(SentryLevel.WARNING, "Profiler not running"); + logger.log(SentryLevel.WARNING, "Profiler not running"); return null; } @@ -240,7 +236,7 @@ public void onFrameMetricCollected( // throws) Debug.stopMethodTracing(); } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error while stopping profiling: ", e); + logger.log(SentryLevel.ERROR, "Error while stopping profiling: ", e); } finally { isRunning = false; } @@ -250,7 +246,7 @@ public void onFrameMetricCollected( long transactionEndCpuMillis = Process.getElapsedCpuTime(); if (traceFile == null) { - options.getLogger().log(SentryLevel.ERROR, "Trace file does not exists"); + logger.log(SentryLevel.ERROR, "Trace file does not exists"); return null; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java index 5b465140b9..3ae5801fc2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java @@ -104,7 +104,8 @@ private void init() { tracesFilesDirPath, (int) SECONDS.toMicros(1) / intervalHz, frameMetricsCollector, - options, + options.getExecutorService(), + options.getLogger(), buildInfoProvider); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java index eb511e933e..2d600ae65c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java @@ -13,6 +13,7 @@ import android.view.FrameMetrics; import android.view.Window; import androidx.annotation.RequiresApi; +import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.BuildInfoProvider; @@ -32,7 +33,7 @@ public final class SentryFrameMetricsCollector implements Application.ActivityLifecycleCallbacks { private final @NotNull BuildInfoProvider buildInfoProvider; private final @NotNull Set trackedWindows = new CopyOnWriteArraySet<>(); - private final @NotNull SentryOptions options; + private final @NotNull ILogger logger; private @Nullable Handler handler; private @Nullable WeakReference currentWindow; private final @NotNull Map listenerMap = @@ -54,6 +55,14 @@ public SentryFrameMetricsCollector( this(context, options, buildInfoProvider, new WindowFrameMetricsManager() {}); } + @SuppressLint("NewApi") + public SentryFrameMetricsCollector( + final @NotNull Context context, + final @NotNull ILogger logger, + final @NotNull BuildInfoProvider buildInfoProvider) { + this(context, logger, buildInfoProvider, new WindowFrameMetricsManager() {}); + } + @SuppressWarnings("deprecation") @SuppressLint({"NewApi", "DiscouragedPrivateApi"}) public SentryFrameMetricsCollector( @@ -61,8 +70,18 @@ public SentryFrameMetricsCollector( final @NotNull SentryOptions options, final @NotNull BuildInfoProvider buildInfoProvider, final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) { + this(context, options.getLogger(), buildInfoProvider, windowFrameMetricsManager); + } + + @SuppressWarnings("deprecation") + @SuppressLint({"NewApi", "DiscouragedPrivateApi"}) + public SentryFrameMetricsCollector( + final @NotNull Context context, + final @NotNull ILogger logger, + final @NotNull BuildInfoProvider buildInfoProvider, + final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) { Objects.requireNonNull(context, "The context is required"); - this.options = Objects.requireNonNull(options, "SentryOptions is required"); + this.logger = Objects.requireNonNull(logger, "Logger is required"); this.buildInfoProvider = Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required"); this.windowFrameMetricsManager = @@ -81,8 +100,7 @@ public SentryFrameMetricsCollector( HandlerThread handlerThread = new HandlerThread("io.sentry.android.core.internal.util.SentryFrameMetricsCollector"); handlerThread.setUncaughtExceptionHandler( - (thread, e) -> - options.getLogger().log(SentryLevel.ERROR, "Error during frames measurements.", e)); + (thread, e) -> logger.log(SentryLevel.ERROR, "Error during frames measurements.", e)); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); @@ -100,12 +118,10 @@ public SentryFrameMetricsCollector( try { choreographer = Choreographer.getInstance(); } catch (Throwable e) { - options - .getLogger() - .log( - SentryLevel.ERROR, - "Error retrieving Choreographer instance. Slow and frozen frames will not be reported.", - e); + logger.log( + SentryLevel.ERROR, + "Error retrieving Choreographer instance. Slow and frozen frames will not be reported.", + e); } }); // Let's get the last frame timestamp from the choreographer private field @@ -113,9 +129,8 @@ public SentryFrameMetricsCollector( choreographerLastFrameTimeField = Choreographer.class.getDeclaredField("mLastFrameTimeNanos"); choreographerLastFrameTimeField.setAccessible(true); } catch (NoSuchFieldException e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Unable to get the frame timestamp from the choreographer: ", e); + logger.log( + SentryLevel.ERROR, "Unable to get the frame timestamp from the choreographer: ", e); } frameMetricsAvailableListener = (window, frameMetrics, dropCountSinceLastInvocation) -> { @@ -247,9 +262,7 @@ private void stopTrackingWindow(final @NotNull Window window) { windowFrameMetricsManager.removeOnFrameMetricsAvailableListener( window, frameMetricsAvailableListener); } catch (Exception e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Failed to remove frameMetricsAvailableListener", e); + logger.log(SentryLevel.ERROR, "Failed to remove frameMetricsAvailableListener", e); } } trackedWindows.remove(window); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt index 0671a83a9c..a726b2c55b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt @@ -9,6 +9,7 @@ import io.sentry.ILogger import io.sentry.ISentryExecutorService import io.sentry.MemoryCollectionData import io.sentry.PerformanceCollectionData +import io.sentry.SentryExecutorService import io.sentry.SentryLevel import io.sentry.android.core.internal.util.SentryFrameMetricsCollector import io.sentry.profilemeasurements.ProfileMeasurement @@ -41,7 +42,7 @@ class AndroidProfilerTest { private lateinit var context: Context private val className = "io.sentry.android.core.AndroidProfiler" - private val ctorTypes = arrayOf(String::class.java, Int::class.java, SentryFrameMetricsCollector::class.java, SentryAndroidOptions::class.java, BuildInfoProvider::class.java) + private val ctorTypes = arrayOf(String::class.java, Int::class.java, SentryFrameMetricsCollector::class.java, ISentryExecutorService::class.java, ILogger::class.java, BuildInfoProvider::class.java) private val fixture = Fixture() private class Fixture { @@ -86,7 +87,14 @@ class AndroidProfilerTest { val frameMetricsCollector: SentryFrameMetricsCollector = mock() fun getSut(interval: Int = 1, buildInfoProvider: BuildInfoProvider = buildInfo): AndroidProfiler { - return AndroidProfiler(options.profilingTracesDirPath!!, interval, frameMetricsCollector, options, buildInfoProvider) + return AndroidProfiler( + options.profilingTracesDirPath!!, + interval, + frameMetricsCollector, + options.executorService, + options.logger, + buildInfoProvider + ) } } @@ -135,16 +143,19 @@ class AndroidProfilerTest { val ctor = className.getCtor(ctorTypes) assertFailsWith { - ctor.newInstance(arrayOf(null, 0, mock(), mock(), mock())) + ctor.newInstance(arrayOf(null, 0, mock(), mock(), mock(), mock())) } assertFailsWith { - ctor.newInstance(arrayOf("mock", 0, null, mock(), mock())) + ctor.newInstance(arrayOf("mock", 0, null, mock(), mock(), mock())) } assertFailsWith { - ctor.newInstance(arrayOf("mock", 0, mock(), null, mock())) + ctor.newInstance(arrayOf("mock", 0, mock(), null, mock(), mock())) } assertFailsWith { - ctor.newInstance(arrayOf("mock", 0, mock(), mock(), null)) + ctor.newInstance(arrayOf("mock", 0, mock(), mock(), null, mock())) + } + assertFailsWith { + ctor.newInstance(arrayOf("mock", 0, mock(), mock(), mock(), null)) } } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a4402bf318..f71cefbfe1 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1782,6 +1782,15 @@ public final class io/sentry/SentryExceptionFactory { public fun getSentryExceptionsFromThread (Lio/sentry/protocol/SentryThread;Lio/sentry/protocol/Mechanism;Ljava/lang/Throwable;)Ljava/util/List; } +public final class io/sentry/SentryExecutorService : io/sentry/ISentryExecutorService { + public fun ()V + public fun close (J)V + public fun isClosed ()Z + public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future; + public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; + public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; +} + public final class io/sentry/SentryInstantDate : io/sentry/SentryDate { public fun ()V public fun (Ljava/time/Instant;)V diff --git a/sentry/src/main/java/io/sentry/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java index 9097cb7fdc..4aa903b350 100644 --- a/sentry/src/main/java/io/sentry/SentryExecutorService.java +++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java @@ -6,10 +6,12 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; -final class SentryExecutorService implements ISentryExecutorService { +@ApiStatus.Internal +public final class SentryExecutorService implements ISentryExecutorService { private final @NotNull ScheduledExecutorService executorService; @@ -18,7 +20,7 @@ final class SentryExecutorService implements ISentryExecutorService { this.executorService = executorService; } - SentryExecutorService() { + public SentryExecutorService() { this(Executors.newSingleThreadScheduledExecutor(new SentryExecutorServiceThreadFactory())); }