From 3d809da4d6f094934785001cd82af71d5b1054f3 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 20 Sep 2024 10:17:25 +0200 Subject: [PATCH 01/10] added profile context to SentryTracer removed isProfilingEnabled from AndroidContinuousProfiler, as it's useless added continuous profiler to SentryOptions --- .../api/sentry-android-core.api | 3 +- .../core/AndroidContinuousProfiler.java | 12 ++-- .../core/AndroidOptionsInitializer.java | 55 +++++++++++++------ .../core/AndroidContinuousProfilerTest.kt | 19 +------ .../core/AndroidOptionsInitializerTest.kt | 27 ++++++++- sentry/api/sentry.api | 6 +- .../java/io/sentry/IContinuousProfiler.java | 4 ++ .../io/sentry/NoOpContinuousProfiler.java | 6 ++ .../main/java/io/sentry/ProfileContext.java | 4 +- .../main/java/io/sentry/SentryOptions.java | 27 ++++++++- .../src/main/java/io/sentry/SentryTracer.java | 7 +++ .../io/sentry/NoOpContinuousProfilerTest.kt | 7 +++ .../test/java/io/sentry/OutboxSenderTest.kt | 1 + .../test/java/io/sentry/SentryOptionsTest.kt | 5 ++ .../test/java/io/sentry/SentryTracerTest.kt | 37 +++++++++++++ 15 files changed, 171 insertions(+), 49 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 23c798b561d..a49e591f1ec 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -37,8 +37,9 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android } public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler { - public fun (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ZILio/sentry/ISentryExecutorService;)V + public fun (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/ISentryExecutorService;)V public fun close ()V + public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z public fun setScopes (Lio/sentry/IScopes;)V public fun start ()V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index b9cd9ca9da1..bab5245921b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -27,7 +27,6 @@ public class AndroidContinuousProfiler implements IContinuousProfiler { private final @NotNull ILogger logger; private final @Nullable String profilingTracesDirPath; - private final boolean isProfilingEnabled; private final int profilingTracesHz; private final @NotNull ISentryExecutorService executorService; private final @NotNull BuildInfoProvider buildInfoProvider; @@ -46,14 +45,12 @@ public AndroidContinuousProfiler( final @NotNull SentryFrameMetricsCollector frameMetricsCollector, final @NotNull ILogger logger, final @Nullable String profilingTracesDirPath, - final boolean isProfilingEnabled, final int profilingTracesHz, final @NotNull ISentryExecutorService executorService) { this.logger = logger; this.frameMetricsCollector = frameMetricsCollector; this.buildInfoProvider = buildInfoProvider; this.profilingTracesDirPath = profilingTracesDirPath; - this.isProfilingEnabled = isProfilingEnabled; this.profilingTracesHz = profilingTracesHz; this.executorService = executorService; } @@ -64,10 +61,6 @@ private void init() { return; } isInitialized = true; - if (!isProfilingEnabled) { - logger.log(SentryLevel.INFO, "Profiling is disabled in options."); - return; - } if (profilingTracesDirPath == null) { logger.log( SentryLevel.WARNING, @@ -189,6 +182,11 @@ public synchronized void close() { stop(); } + @Override + public @NotNull SentryId getProfilerId() { + return profilerId; + } + private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { try { options diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index b5b0708164e..e5a66dc9ef1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -10,6 +10,8 @@ import io.sentry.ILogger; import io.sentry.ITransactionProfiler; import io.sentry.NoOpConnectionStatusProvider; +import io.sentry.NoOpContinuousProfiler; +import io.sentry.NoOpTransactionProfiler; import io.sentry.ScopeType; import io.sentry.SendFireAndForgetEnvelopeSender; import io.sentry.SendFireAndForgetOutboxSender; @@ -158,26 +160,43 @@ static void initializeIntegrationsAndProcessors( options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider)); options.setTransportGate(new AndroidTransportGate(options)); - // Check if the profiler was already instantiated in the app start. - // We use the Android profiler, that uses a global start/stop api, so we need to preserve the - // state of the profiler, and it's only possible retaining the instance. - synchronized (AppStartMetrics.getInstance()) { - final @Nullable ITransactionProfiler appStartProfiler = - AppStartMetrics.getInstance().getAppStartProfiler(); - if (appStartProfiler != null) { - options.setTransactionProfiler(appStartProfiler); - AppStartMetrics.getInstance().setAppStartProfiler(null); - } else { - options.setTransactionProfiler( - new AndroidTransactionProfiler( - context, - options, - buildInfoProvider, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"))); + if (options.isProfilingEnabled()) { + options.setContinuousProfiler(NoOpContinuousProfiler.getInstance()); + // Check if the profiler was already instantiated in the app start. + // We use the Android profiler, that uses a global start/stop api, so we need to preserve the + // state of the profiler, and it's only possible retaining the instance. + synchronized (AppStartMetrics.getInstance()) { + final @Nullable ITransactionProfiler appStartProfiler = + AppStartMetrics.getInstance().getAppStartProfiler(); + if (appStartProfiler != null) { + options.setTransactionProfiler(appStartProfiler); + AppStartMetrics.getInstance().setAppStartProfiler(null); + } else { + options.setTransactionProfiler( + new AndroidTransactionProfiler( + context, + options, + buildInfoProvider, + Objects.requireNonNull( + options.getFrameMetricsCollector(), + "options.getFrameMetricsCollector is required"))); + } } + } else { + options.setTransactionProfiler(NoOpTransactionProfiler.getInstance()); + // todo handle app start continuous profiler + options.setContinuousProfiler( + new AndroidContinuousProfiler( + buildInfoProvider, + Objects.requireNonNull( + options.getFrameMetricsCollector(), + "options.getFrameMetricsCollector is required"), + options.getLogger(), + options.getProfilingTracesDirPath(), + options.getProfilingTracesHz(), + options.getExecutorService())); } + options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger())); options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger())); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index 5878354e70f..bce52db6a5b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -70,7 +70,6 @@ class AndroidContinuousProfilerTest { frameMetricsCollector, options.logger, options.profilingTracesDirPath, - options.isProfilingEnabled, options.profilingTracesHz, options.executorService ).also { it.setScopes(scopes) } @@ -149,26 +148,12 @@ class AndroidContinuousProfilerTest { } @Test - fun `profiler on profilesSampleRate=0 false`() { + fun `profiler ignores profilesSampleRate`() { val profiler = fixture.getSut { it.profilesSampleRate = 0.0 } profiler.start() - assertFalse(profiler.isRunning) - } - - @Test - fun `profiler evaluates if profiling is enabled in options only on first start`() { - // We create the profiler, and nothing goes wrong - val profiler = fixture.getSut { - it.profilesSampleRate = 0.0 - } - verify(fixture.mockLogger, never()).log(SentryLevel.INFO, "Profiling is disabled in options.") - - // Regardless of how many times the profiler is started, the option is evaluated and logged only once - profiler.start() - profiler.start() - verify(fixture.mockLogger, times(1)).log(SentryLevel.INFO, "Profiling is disabled in options.") + assertTrue(profiler.isRunning) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index ed2fa3338a5..6dd761f43f7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -9,6 +9,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.DefaultTransactionPerformanceCollector import io.sentry.ILogger import io.sentry.MainEventProcessor +import io.sentry.NoOpContinuousProfiler +import io.sentry.NoOpTransactionProfiler import io.sentry.SentryOptions import io.sentry.android.core.cache.AndroidEnvelopeCache import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator @@ -345,11 +347,34 @@ class AndroidOptionsInitializerTest { } @Test - fun `init should set Android transaction profiler`() { + fun `init should set Android continuous profiler`() { fixture.initSut() + assertNotNull(fixture.sentryOptions.transactionProfiler) + assertEquals(fixture.sentryOptions.transactionProfiler, NoOpTransactionProfiler.getInstance()) + assertTrue(fixture.sentryOptions.continuousProfiler is AndroidContinuousProfiler) + } + + @Test + fun `init with profilesSampleRate should set Android transaction profiler`() { + fixture.initSut(configureOptions = { + profilesSampleRate = 1.0 + }) + + assertNotNull(fixture.sentryOptions.transactionProfiler) + assertTrue(fixture.sentryOptions.transactionProfiler is AndroidTransactionProfiler) + assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) + } + + @Test + fun `init with profilesSampler should set Android transaction profiler`() { + fixture.initSut(configureOptions = { + profilesSampler = mock() + }) + assertNotNull(fixture.sentryOptions.transactionProfiler) assertTrue(fixture.sentryOptions.transactionProfiler is AndroidTransactionProfiler) + assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) } @Test diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index b5815cb08b9..5faf1343aa2 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -702,6 +702,7 @@ public abstract interface class io/sentry/IConnectionStatusProvider$IConnectionS public abstract interface class io/sentry/IContinuousProfiler { public abstract fun close ()V + public abstract fun getProfilerId ()Lio/sentry/protocol/SentryId; public abstract fun isRunning ()Z public abstract fun setScopes (Lio/sentry/IScopes;)V public abstract fun start ()V @@ -1401,6 +1402,7 @@ public final class io/sentry/NoOpConnectionStatusProvider : io/sentry/IConnectio public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfiler { public fun close ()V public static fun getInstance ()Lio/sentry/NoOpContinuousProfiler; + public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z public fun setScopes (Lio/sentry/IScopes;)V public fun start ()V @@ -1900,7 +1902,7 @@ public final class io/sentry/ProfileChunk$JsonKeys { public fun ()V } -public class io/sentry/ProfileContext : io/sentry/JsonSerializable, io/sentry/JsonUnknown { +public final class io/sentry/ProfileContext : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public static final field TYPE Ljava/lang/String; public fun ()V public fun (Lio/sentry/ProfileContext;)V @@ -2951,6 +2953,7 @@ public class io/sentry/SentryOptions { public fun getConnectionStatusProvider ()Lio/sentry/IConnectionStatusProvider; public fun getConnectionTimeoutMillis ()I public fun getContextTags ()Ljava/util/List; + public fun getContinuousProfiler ()Lio/sentry/IContinuousProfiler; public fun getCron ()Lio/sentry/SentryOptions$Cron; public fun getDateProvider ()Lio/sentry/SentryDateProvider; public fun getDebugMetaLoader ()Lio/sentry/internal/debugmeta/IDebugMetaLoader; @@ -3069,6 +3072,7 @@ public class io/sentry/SentryOptions { public fun setCacheDirPath (Ljava/lang/String;)V public fun setConnectionStatusProvider (Lio/sentry/IConnectionStatusProvider;)V public fun setConnectionTimeoutMillis (I)V + public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V public fun setCron (Lio/sentry/SentryOptions$Cron;)V public fun setDateProvider (Lio/sentry/SentryDateProvider;)V public fun setDebug (Z)V diff --git a/sentry/src/main/java/io/sentry/IContinuousProfiler.java b/sentry/src/main/java/io/sentry/IContinuousProfiler.java index c94eb9bba3a..3fe19f614bb 100644 --- a/sentry/src/main/java/io/sentry/IContinuousProfiler.java +++ b/sentry/src/main/java/io/sentry/IContinuousProfiler.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.protocol.SentryId; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -16,4 +17,7 @@ public interface IContinuousProfiler { /** Cancel the profiler and stops it. Used on SDK close. */ void close(); + + @NotNull + SentryId getProfilerId(); } diff --git a/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java b/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java index b17123029f6..0ae5d6d81ba 100644 --- a/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java +++ b/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.protocol.SentryId; import org.jetbrains.annotations.NotNull; public final class NoOpContinuousProfiler implements IContinuousProfiler { @@ -28,4 +29,9 @@ public boolean isRunning() { @Override public void close() {} + + @Override + public @NotNull SentryId getProfilerId() { + return SentryId.EMPTY_ID; + } } diff --git a/sentry/src/main/java/io/sentry/ProfileContext.java b/sentry/src/main/java/io/sentry/ProfileContext.java index 3a7fa9af2f2..e4b411c279a 100644 --- a/sentry/src/main/java/io/sentry/ProfileContext.java +++ b/sentry/src/main/java/io/sentry/ProfileContext.java @@ -1,6 +1,5 @@ package io.sentry; -import com.jakewharton.nopen.annotation.Open; import io.sentry.protocol.SentryId; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; @@ -11,8 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -@Open -public class ProfileContext implements JsonUnknown, JsonSerializable { +public final class ProfileContext implements JsonUnknown, JsonSerializable { public static final String TYPE = "profile"; /** Determines which trace the Span belongs to. */ diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index c18714558c6..e40c78b1a05 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -350,9 +350,12 @@ public class SentryOptions { /** Max trace file size in bytes. */ private long maxTraceFileSize = 5 * 1024 * 1024; - /** Listener interface to perform operations when a transaction is started or ended */ + /** Profiler that runs when a transaction is started until it's finished. */ private @NotNull ITransactionProfiler transactionProfiler = NoOpTransactionProfiler.getInstance(); + /** Profiler that runs continuously until stopped. */ + private @NotNull IContinuousProfiler continuousProfiler = NoOpContinuousProfiler.getInstance(); + /** * Contains a list of origins to which `sentry-trace` header should be sent in HTTP integrations. */ @@ -1697,6 +1700,28 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact } } + /** + * Returns the continuous profiler. + * + * @return the continuous profiler. + */ + public @NotNull IContinuousProfiler getContinuousProfiler() { + return continuousProfiler; + } + + /** + * Sets the continuous profiler. It only has effect if no profiler was already set. + * + * @param continuousProfiler - the continuous profiler + */ + public void setContinuousProfiler(final @Nullable IContinuousProfiler continuousProfiler) { + // We allow to set the profiler only if it was not set before, and we don't allow to unset it. + if (this.continuousProfiler == NoOpContinuousProfiler.getInstance() + && continuousProfiler != null) { + this.continuousProfiler = continuousProfiler; + } + } + /** * Returns if profiling is enabled for transactions. * diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index d27f398b9b3..218a5018591 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -85,6 +85,12 @@ public SentryTracer( this.baggage = new Baggage(scopes.getOptions().getLogger()); } + final @NotNull SentryId continuousProfilerId = + scopes.getOptions().getContinuousProfiler().getProfilerId(); + if (!continuousProfilerId.equals(SentryId.EMPTY_ID)) { + this.contexts.setProfile(new ProfileContext(continuousProfilerId)); + } + // We are currently sending the performance data only in profiles, but we are always sending // performance measurements. if (transactionPerformanceCollector != null) { @@ -514,6 +520,7 @@ private ISpan createChild( // } // }); // span.setDescription(description); + //todo scopes.getOptions().getThreadChecker().currentThreadSystemId() span.setData(SpanDataConvention.THREAD_ID, String.valueOf(Thread.currentThread().getId())); span.setData( SpanDataConvention.THREAD_NAME, diff --git a/sentry/src/test/java/io/sentry/NoOpContinuousProfilerTest.kt b/sentry/src/test/java/io/sentry/NoOpContinuousProfilerTest.kt index afbce4a8cb9..e791651aefd 100644 --- a/sentry/src/test/java/io/sentry/NoOpContinuousProfilerTest.kt +++ b/sentry/src/test/java/io/sentry/NoOpContinuousProfilerTest.kt @@ -1,6 +1,8 @@ package io.sentry +import io.sentry.protocol.SentryId import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse class NoOpContinuousProfilerTest { @@ -22,4 +24,9 @@ class NoOpContinuousProfilerTest { @Test fun `close does not throw`() = profiler.close() + + @Test + fun `getProfilerId returns Empty SentryId`() { + assertEquals(profiler.profilerId, SentryId.EMPTY_ID) + } } diff --git a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt index 62a9eca5186..03d5e21abc7 100644 --- a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt +++ b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt @@ -39,6 +39,7 @@ class OutboxSenderTest { whenever(options.dsn).thenReturn("https://key@sentry.io/proj") whenever(options.dateProvider).thenReturn(SentryNanotimeDateProvider()) whenever(options.mainThreadChecker).thenReturn(NoOpMainThreadChecker.getInstance()) + whenever(options.continuousProfiler).thenReturn(NoOpContinuousProfiler.getInstance()) whenever(scopes.options).thenReturn(this.options) } diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index e12b09e1d52..0a5c6481a9b 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -304,6 +304,11 @@ class SentryOptionsTest { assert(SentryOptions().transactionProfiler == NoOpTransactionProfiler.getInstance()) } + @Test + fun `when options is initialized, continuousProfiler is noop`() { + assert(SentryOptions().continuousProfiler == NoOpContinuousProfiler.getInstance()) + } + @Test fun `when options is initialized, collector is empty list`() { assertTrue(SentryOptions().performanceCollectors.isEmpty()) diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 64e6741d678..01c815ce463 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -199,6 +199,43 @@ class SentryTracerTest { verify(transactionProfiler).onTransactionFinish(any(), anyOrNull(), anyOrNull()) } + @Test + fun `when continuous profiler is running, profile context is set`() { + val continuousProfiler = mock() + val profilerId = SentryId() + whenever(continuousProfiler.profilerId).thenReturn(profilerId) + val tracer = fixture.getSut(optionsConfiguration = { + it.setContinuousProfiler(continuousProfiler) + }) + tracer.finish() + verify(fixture.scopes).captureTransaction( + check { + assertNotNull(it.contexts.profile) { + assertEquals(profilerId, it.profilerId) + } + }, + anyOrNull(), + anyOrNull(), + anyOrNull() + ) + } + + @Test + fun `when continuous profiler is not running, profile context is not set`() { + val tracer = fixture.getSut(optionsConfiguration = { + it.setContinuousProfiler(NoOpContinuousProfiler.getInstance()) + }) + tracer.finish() + verify(fixture.scopes).captureTransaction( + check { + assertNull(it.contexts.profile) + }, + anyOrNull(), + anyOrNull(), + anyOrNull() + ) + } + @Test fun `when transaction is finished, transaction is cleared from the scope`() { val tracer = fixture.getSut() From f1a0a5539cb755c38f898d33ea3269a9b6f58d25 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 26 Sep 2024 12:54:33 +0200 Subject: [PATCH 02/10] added DefaultTransactionPerformanceCollector to AndroidContinuousProfiler updated DefaultTransactionPerformanceCollector to work with string ids other than transactions fixed ProfileChunk measurements being modifiable from other code added thread id and name to SpanContext.data --- .../core/AndroidContinuousProfiler.java | 33 +++++++--- .../core/AndroidContinuousProfilerTest.kt | 62 ++++++++++++++++++- sentry/api/sentry.api | 7 +++ ...efaultTransactionPerformanceCollector.java | 31 ++++++++-- .../NoOpTransactionPerformanceCollector.java | 8 +++ .../src/main/java/io/sentry/ProfileChunk.java | 2 +- .../src/main/java/io/sentry/SpanContext.java | 6 ++ .../TransactionPerformanceCollector.java | 9 +++ ...aultTransactionPerformanceCollectorTest.kt | 61 +++++++++++++++++- .../test/java/io/sentry/SentryTracerTest.kt | 8 ++- .../test/java/io/sentry/SpanContextTest.kt | 7 +++ 11 files changed, 210 insertions(+), 24 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index bab5245921b..5cf09ee20d2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -8,9 +8,11 @@ import io.sentry.ILogger; import io.sentry.IScopes; import io.sentry.ISentryExecutorService; +import io.sentry.PerformanceCollectionData; import io.sentry.ProfileChunk; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.TransactionPerformanceCollector; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.SentryId; import java.util.ArrayList; @@ -35,7 +37,8 @@ public class AndroidContinuousProfiler implements IContinuousProfiler { private @Nullable AndroidProfiler profiler = null; private boolean isRunning = false; private @Nullable IScopes scopes; - private @Nullable Future closeFuture; + private @Nullable Future stopFuture; + private @Nullable TransactionPerformanceCollector performanceCollector; private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; @@ -87,6 +90,7 @@ private void init() { public synchronized void setScopes(final @NotNull IScopes scopes) { this.scopes = scopes; + this.performanceCollector = scopes.getOptions().getTransactionPerformanceCollector(); } public synchronized void start() { @@ -117,7 +121,11 @@ public synchronized void start() { chunkId = new SentryId(); } - closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); + if (performanceCollector != null) { + performanceCollector.start(chunkId.toString()); + } + + stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); } public synchronized void stop() { @@ -126,8 +134,8 @@ public synchronized void stop() { @SuppressLint("NewApi") private synchronized void stop(final boolean restartProfiler) { - if (closeFuture != null) { - closeFuture.cancel(true); + if (stopFuture != null) { + stopFuture.cancel(true); } // check if profiler was created and it's running if (profiler == null || !isRunning) { @@ -140,8 +148,13 @@ private synchronized void stop(final boolean restartProfiler) { return; } - // todo add PerformanceCollectionData - final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(false, null); + List performanceCollectionData = null; + if (performanceCollector != null) { + performanceCollectionData = performanceCollector.stop(chunkId.toString()); + } + + final AndroidProfiler.ProfileEndData endData = + profiler.endAndCollect(false, performanceCollectionData); // check if profiler end successfully if (endData == null) { @@ -176,8 +189,8 @@ private synchronized void stop(final boolean restartProfiler) { } public synchronized void close() { - if (closeFuture != null) { - closeFuture.cancel(true); + if (stopFuture != null) { + stopFuture.cancel(true); } stop(); } @@ -216,7 +229,7 @@ public boolean isRunning() { @VisibleForTesting @Nullable - Future getCloseFuture() { - return closeFuture; + Future getStopFuture() { + return stopFuture; } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index bce52db6a5b..ebad066e7db 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -4,17 +4,24 @@ import android.content.Context import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.CpuCollectionData import io.sentry.ILogger import io.sentry.IScopes import io.sentry.ISentryExecutorService +import io.sentry.MemoryCollectionData +import io.sentry.PerformanceCollectionData import io.sentry.SentryLevel +import io.sentry.SentryNanotimeDate import io.sentry.SentryTracer import io.sentry.TransactionContext +import io.sentry.TransactionPerformanceCollector import io.sentry.android.core.internal.util.SentryFrameMetricsCollector +import io.sentry.profilemeasurements.ProfileMeasurement import io.sentry.test.DeferredExecutorService import io.sentry.test.getProperty import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.check import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -28,6 +35,7 @@ import java.util.concurrent.Future import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -254,6 +262,27 @@ class AndroidContinuousProfilerTest { verify(fixture.mockLogger).log(eq(SentryLevel.ERROR), eq("Error while stopping profiling: "), any()) } + @Test + fun `profiler starts performance collector on start`() { + val performanceCollector = mock() + fixture.options.transactionPerformanceCollector = performanceCollector + val profiler = fixture.getSut() + verify(performanceCollector, never()).start(any()) + profiler.start() + verify(performanceCollector).start(any()) + } + + @Test + fun `profiler stops performance collector on stop`() { + val performanceCollector = mock() + fixture.options.transactionPerformanceCollector = performanceCollector + val profiler = fixture.getSut() + profiler.start() + verify(performanceCollector, never()).stop(any()) + profiler.stop() + verify(performanceCollector).stop(any()) + } + @Test fun `profiler stops collecting frame metrics when it stops`() { val profiler = fixture.getSut() @@ -279,9 +308,9 @@ class AndroidContinuousProfilerTest { val scheduledJob = androidProfiler?.getProperty?>("scheduledFinish") assertNull(scheduledJob) - val closeFuture = profiler.closeFuture - assertNotNull(closeFuture) - assertTrue(closeFuture.isCancelled) + val stopFuture = profiler.stopFuture + assertNotNull(stopFuture) + assertTrue(stopFuture.isCancelled) } @Test @@ -318,6 +347,33 @@ class AndroidContinuousProfilerTest { verify(fixture.scopes).captureProfileChunk(any()) } + @Test + fun `profiler sends chunk with measurements`() { + val executorService = DeferredExecutorService() + val performanceCollector = mock() + val collectionData = PerformanceCollectionData() + + collectionData.addMemoryData(MemoryCollectionData(1, 2, 3, SentryNanotimeDate())) + collectionData.addCpuData(CpuCollectionData(1, 3.0, SentryNanotimeDate())) + whenever(performanceCollector.stop(any())).thenReturn(listOf(collectionData)) + + fixture.options.transactionPerformanceCollector = performanceCollector + val profiler = fixture.getSut { + it.executorService = executorService + } + profiler.start() + profiler.stop() + // We run the executor service to send the profile chunk + executorService.runAll() + verify(fixture.scopes).captureProfileChunk( + check { + assertContains(it.measurements, ProfileMeasurement.ID_CPU_USAGE) + assertContains(it.measurements, ProfileMeasurement.ID_MEMORY_FOOTPRINT) + assertContains(it.measurements, ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT) + } + ) + } + @Test fun `profiler sends another chunk on stop`() { val executorService = DeferredExecutorService() diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ba3572e0b9b..93b5cb83c1e 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -384,7 +384,9 @@ public final class io/sentry/DefaultTransactionPerformanceCollector : io/sentry/ public fun onSpanFinished (Lio/sentry/ISpan;)V public fun onSpanStarted (Lio/sentry/ISpan;)V public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; } public final class io/sentry/DiagnosticLogger : io/sentry/ILogger { @@ -1764,7 +1766,9 @@ public final class io/sentry/NoOpTransactionPerformanceCollector : io/sentry/Tra public fun onSpanFinished (Lio/sentry/ISpan;)V public fun onSpanStarted (Lio/sentry/ISpan;)V public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; } public final class io/sentry/NoOpTransactionProfiler : io/sentry/ITransactionProfiler { @@ -3553,6 +3557,7 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU public static final field DEFAULT_ORIGIN Ljava/lang/String; public static final field TYPE Ljava/lang/String; protected field baggage Lio/sentry/Baggage; + protected final field data Ljava/util/Map; protected field description Ljava/lang/String; protected field op Ljava/lang/String; protected field origin Ljava/lang/String; @@ -3819,7 +3824,9 @@ public abstract interface class io/sentry/TransactionPerformanceCollector { public abstract fun onSpanFinished (Lio/sentry/ISpan;)V public abstract fun onSpanStarted (Lio/sentry/ISpan;)V public abstract fun start (Lio/sentry/ITransaction;)V + public abstract fun start (Ljava/lang/String;)V public abstract fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public abstract fun stop (Ljava/lang/String;)Ljava/util/List; } public final class io/sentry/TypeCheckHint { diff --git a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java index 9839569dc20..53efe29ee60 100644 --- a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java @@ -81,6 +81,23 @@ public void start(final @NotNull ITransaction transaction) { e); } } + start(transaction.getEventId().toString()); + } + + @Override + public void start(final @NotNull String id) { + if (hasNoCollectors) { + options + .getLogger() + .log( + SentryLevel.INFO, + "No collector found. Performance stats will not be captured during transactions."); + return; + } + + if (!performanceDataMap.containsKey(id)) { + performanceDataMap.put(id, new ArrayList<>()); + } if (!isStarted.getAndSet(true)) { synchronized (timerLock) { if (timer == null) { @@ -109,7 +126,7 @@ public void run() { // The timer is scheduled to run every 100ms on average. In case it takes longer, // subsequent tasks are executed more quickly. If two tasks are scheduled to run in // less than 10ms, the measurement that we collect is not meaningful, so we skip it - if (now - lastCollectionTimestamp < 10) { + if (now - lastCollectionTimestamp <= 10) { return; } lastCollectionTimestamp = now; @@ -156,14 +173,18 @@ public void onSpanFinished(@NotNull ISpan span) { transaction.getName(), transaction.getSpanContext().getTraceId().toString()); - final @Nullable List data = - performanceDataMap.remove(transaction.getEventId().toString()); - for (final @NotNull IPerformanceContinuousCollector collector : continuousCollectors) { collector.onSpanFinished(transaction); } - // close if they are no more remaining transactions + return stop(transaction.getEventId().toString()); + } + + @Override + public @Nullable List stop(final @NotNull String id) { + final @Nullable List data = performanceDataMap.remove(id); + + // close if they are no more running requests if (performanceDataMap.isEmpty()) { close(); } diff --git a/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java index abf5ec5f6ae..91533069d71 100644 --- a/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java @@ -18,6 +18,9 @@ private NoOpTransactionPerformanceCollector() {} @Override public void start(@NotNull ITransaction transaction) {} + @Override + public void start(@NotNull String id) {} + @Override public void onSpanStarted(@NotNull ISpan span) {} @@ -29,6 +32,11 @@ public void onSpanFinished(@NotNull ISpan span) {} return null; } + @Override + public @Nullable List stop(@NotNull String id) { + return null; + } + @Override public void close() {} } diff --git a/sentry/src/main/java/io/sentry/ProfileChunk.java b/sentry/src/main/java/io/sentry/ProfileChunk.java index 44cb9212091..725c151dbd0 100644 --- a/sentry/src/main/java/io/sentry/ProfileChunk.java +++ b/sentry/src/main/java/io/sentry/ProfileChunk.java @@ -160,7 +160,7 @@ public Builder( final @NotNull File traceFile) { this.profilerId = profilerId; this.chunkId = chunkId; - this.measurements = measurements; + this.measurements = new ConcurrentHashMap<>(measurements); this.traceFile = traceFile; } diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index f983b3a61c6..858f757a9b2 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -41,6 +41,8 @@ public class SpanContext implements JsonUnknown, JsonSerializable { /** Describes the status of the Transaction. */ protected @Nullable SpanStatus status; + protected final @NotNull Map data = new ConcurrentHashMap<>(); + /** A map or list of tags for this event. Each tag must be less than 200 characters. */ protected @NotNull Map tags = new ConcurrentHashMap<>(); @@ -94,6 +96,10 @@ public SpanContext( this.description = description; this.status = status; this.origin = origin; + final long threadId = + ScopesAdapter.getInstance().getOptions().getThreadChecker().currentThreadSystemId(); + this.data.put(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); + this.data.put(SpanDataConvention.THREAD_NAME, Thread.currentThread().getName()); } /** diff --git a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java index 7880d611975..300e771b5df 100644 --- a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java @@ -7,8 +7,12 @@ public interface TransactionPerformanceCollector { + /** Starts collecting performance data and span related data (e.g. slow/frozen frames). */ void start(@NotNull ITransaction transaction); + /** Starts collecting performance data without span related data (e.g. slow/frozen frames). */ + void start(@NotNull String id); + /** * Called whenever a new span (including the top level transaction) is started. * @@ -23,9 +27,14 @@ public interface TransactionPerformanceCollector { */ void onSpanFinished(@NotNull ISpan span); + /** Stops collecting performance data and span related data (e.g. slow/frozen frames). */ @Nullable List stop(@NotNull ITransaction transaction); + /** Stops collecting performance data. */ + @Nullable + List stop(@NotNull String id); + /** Cancel the collector and stops it. Used on SDK close. */ @ApiStatus.Internal void close(); diff --git a/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt b/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt index 60005935c94..1a8f3bfc04e 100644 --- a/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt +++ b/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt @@ -33,6 +33,7 @@ class DefaultTransactionPerformanceCollectorTest { private class Fixture { lateinit var transaction1: ITransaction lateinit var transaction2: ITransaction + val id1 = "id1" val scopes: IScopes = mock() val options = SentryOptions() var mockTimer: Timer? = null @@ -104,6 +105,13 @@ class DefaultTransactionPerformanceCollectorTest { verify(fixture.mockTimer)!!.scheduleAtFixedRate(any(), any(), eq(100)) } + @Test + fun `when start with a string, timer is scheduled every 100 milliseconds`() { + val collector = fixture.getSut() + collector.start(fixture.id1) + verify(fixture.mockTimer)!!.scheduleAtFixedRate(any(), any(), eq(100)) + } + @Test fun `when stop, timer is stopped`() { val collector = fixture.getSut() @@ -113,6 +121,15 @@ class DefaultTransactionPerformanceCollectorTest { verify(fixture.mockTimer)!!.cancel() } + @Test + fun `when stop with a string, timer is stopped`() { + val collector = fixture.getSut() + collector.start(fixture.id1) + collector.stop(fixture.id1) + verify(fixture.mockTimer)!!.scheduleAtFixedRate(any(), any(), eq(100)) + verify(fixture.mockTimer)!!.cancel() + } + @Test fun `stopping a not collected transaction return null`() { val collector = fixture.getSut() @@ -122,34 +139,53 @@ class DefaultTransactionPerformanceCollectorTest { assertNull(data) } + @Test + fun `stopping a not collected id return null`() { + val collector = fixture.getSut() + val data = collector.stop(fixture.id1) + verify(fixture.mockTimer, never())!!.scheduleAtFixedRate(any(), any(), eq(100)) + verify(fixture.mockTimer, never())!!.cancel() + assertNull(data) + } + @Test fun `collector collect memory for multiple transactions`() { val collector = fixture.getSut() collector.start(fixture.transaction1) collector.start(fixture.transaction2) + collector.start(fixture.id1) // Let's sleep to make the collector get values Thread.sleep(300) val data1 = collector.stop(fixture.transaction1) - // There is still a transaction running: the timer shouldn't stop now + // There is still a transaction and an id running: the timer shouldn't stop now verify(fixture.mockTimer, never())!!.cancel() val data2 = collector.stop(fixture.transaction2) - // There are no more transactions running: the time should stop now + // There is still an id running: the timer shouldn't stop now + verify(fixture.mockTimer, never())!!.cancel() + + val data3 = collector.stop(fixture.id1) + // There are no more transactions or ids running: the time should stop now verify(fixture.mockTimer)!!.cancel() assertNotNull(data1) assertNotNull(data2) + assertNotNull(data3) val memoryData1 = data1.map { it.memoryData } val cpuData1 = data1.map { it.cpuData } val memoryData2 = data2.map { it.memoryData } val cpuData2 = data2.map { it.cpuData } + val memoryData3 = data3.map { it.memoryData } + val cpuData3 = data3.map { it.cpuData } // The data returned by the collector is not empty assertFalse(memoryData1.isEmpty()) assertFalse(cpuData1.isEmpty()) assertFalse(memoryData2.isEmpty()) assertFalse(cpuData2.isEmpty()) + assertFalse(memoryData3.isEmpty()) + assertFalse(cpuData3.isEmpty()) } @Test @@ -266,6 +302,27 @@ class DefaultTransactionPerformanceCollectorTest { verify(collector).clear() } + @Test + fun `Continuous collectors are not called when collecting using a string id`() { + val collector = mock() + fixture.options.performanceCollectors.add(collector) + val sut = fixture.getSut(memoryCollector = null, cpuCollector = null) + + // when a collection is started with an id + sut.start(fixture.id1) + + // collector should not be notified + verify(collector, never()).onSpanStarted(fixture.transaction1) + + // when the id collection is stopped + sut.stop(fixture.id1) + + // collector should not be notified + verify(collector, never()).onSpanFinished(fixture.transaction1) + + verify(collector).clear() + } + @Test fun `Continuous collectors are notified properly even when multiple txn are running`() { val collector = mock() diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 91aeac839b9..01ab21a37bc 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -1065,7 +1065,7 @@ class SentryTracerTest { @Test fun `when transaction is created, but not profiled, transactionPerformanceCollector is started anyway`() { val transaction = fixture.getSut() - verify(fixture.transactionPerformanceCollector).start(anyOrNull()) + verify(fixture.transactionPerformanceCollector).start(anyOrNull()) } @Test @@ -1073,14 +1073,14 @@ class SentryTracerTest { val transaction = fixture.getSut(optionsConfiguration = { it.profilesSampleRate = 1.0 }, samplingDecision = TracesSamplingDecision(true, null, true, null)) - verify(fixture.transactionPerformanceCollector).start(check { assertEquals(transaction, it) }) + verify(fixture.transactionPerformanceCollector).start(check { assertEquals(transaction, it) }) } @Test fun `when transaction is finished, transactionPerformanceCollector is stopped`() { val transaction = fixture.getSut() transaction.finish() - verify(fixture.transactionPerformanceCollector).stop(check { assertEquals(transaction, it) }) + verify(fixture.transactionPerformanceCollector).stop(check { assertEquals(transaction, it) }) } @Test @@ -1247,9 +1247,11 @@ class SentryTracerTest { val data = mutableListOf(mock(), mock()) val mockPerformanceCollector = object : TransactionPerformanceCollector { override fun start(transaction: ITransaction) {} + override fun start(id: String) {} override fun onSpanStarted(span: ISpan) {} override fun onSpanFinished(span: ISpan) {} override fun stop(transaction: ITransaction): MutableList = data + override fun stop(id: String): MutableList = data override fun close() {} } val transaction = fixture.getSut(optionsConfiguration = { diff --git a/sentry/src/test/java/io/sentry/SpanContextTest.kt b/sentry/src/test/java/io/sentry/SpanContextTest.kt index 5e7ba9de254..47b98d5ee86 100644 --- a/sentry/src/test/java/io/sentry/SpanContextTest.kt +++ b/sentry/src/test/java/io/sentry/SpanContextTest.kt @@ -13,6 +13,13 @@ class SpanContextTest { assertNotNull(trace.spanId) } + @Test + fun `when created with default constructor, generates thread id and name`() { + val trace = SpanContext("op") + assertNotNull(trace.data[SpanDataConvention.THREAD_ID]) + assertNotNull(trace.data[SpanDataConvention.THREAD_NAME]) + } + @Test fun `sets tag`() { val trace = SpanContext("op") From b0b1927bdc87c48b15d6fbe0d64ac9c718cf43c1 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 26 Sep 2024 13:15:26 +0200 Subject: [PATCH 03/10] added profiler_id to span data --- .../src/main/java/io/sentry/SentryTracer.java | 4 ++++ .../src/main/java/io/sentry/SpanContext.java | 11 +++++++++- .../java/io/sentry/SpanDataConvention.java | 1 + .../test/java/io/sentry/SentryTracerTest.kt | 22 +++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 8729cee2613..d2956ba9b10 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -519,6 +519,10 @@ private ISpan createChild( // }); // span.setDescription(description); final long threadId = scopes.getOptions().getThreadChecker().currentThreadSystemId(); + final SentryId profilerId = scopes.getOptions().getContinuousProfiler().getProfilerId(); + if (!profilerId.equals(SentryId.EMPTY_ID)) { + span.setData(SpanDataConvention.PROFILER_ID, profilerId.toString()); + } span.setData(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); span.setData( SpanDataConvention.THREAD_NAME, diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index 858f757a9b2..dcd8b9bebec 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -41,7 +41,7 @@ public class SpanContext implements JsonUnknown, JsonSerializable { /** Describes the status of the Transaction. */ protected @Nullable SpanStatus status; - protected final @NotNull Map data = new ConcurrentHashMap<>(); + protected final @NotNull Map data; /** A map or list of tags for this event. Each tag must be less than 200 characters. */ protected @NotNull Map tags = new ConcurrentHashMap<>(); @@ -98,6 +98,7 @@ public SpanContext( this.origin = origin; final long threadId = ScopesAdapter.getInstance().getOptions().getThreadChecker().currentThreadSystemId(); + this.data = new ConcurrentHashMap<>(); this.data.put(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); this.data.put(SpanDataConvention.THREAD_NAME, Thread.currentThread().getName()); } @@ -125,6 +126,14 @@ public SpanContext(final @NotNull SpanContext spanContext) { if (copiedUnknown != null) { this.unknown = copiedUnknown; } + this.instrumenter = spanContext.instrumenter; + this.baggage = spanContext.baggage; + final Map copiedData = CollectionUtils.newConcurrentHashMap(spanContext.data); + if (copiedData != null) { + this.data = copiedData; + } else { + this.data = new ConcurrentHashMap<>(); + } } public void setOperation(final @NotNull String operation) { diff --git a/sentry/src/main/java/io/sentry/SpanDataConvention.java b/sentry/src/main/java/io/sentry/SpanDataConvention.java index ffe2414af39..c4329f6dcad 100644 --- a/sentry/src/main/java/io/sentry/SpanDataConvention.java +++ b/sentry/src/main/java/io/sentry/SpanDataConvention.java @@ -25,4 +25,5 @@ public interface SpanDataConvention { String CONTRIBUTES_TTFD = "ui.contributes_to_ttfd"; String HTTP_START_TIMESTAMP = "http.start_timestamp"; String HTTP_END_TIMESTAMP = "http.end_timestamp"; + String PROFILER_ID = "profiler_id"; } diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 01ab21a37bc..f6d6f8596d6 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -246,6 +246,28 @@ class SentryTracerTest { ) } + @Test + fun `when continuous profiler is running, profiler id is set in span data`() { + val profilerId = SentryId() + val profiler = mock() + whenever(profiler.profilerId).thenReturn(profilerId) + + val tracer = fixture.getSut(optionsConfiguration = { options -> + options.setContinuousProfiler(profiler) + }) + val span = tracer.startChild("span.op") + assertEquals(profilerId.toString(), span.getData(SpanDataConvention.PROFILER_ID)) + } + + @Test + fun `when continuous profiler is not running, profiler id is not set in span data`() { + val tracer = fixture.getSut(optionsConfiguration = { options -> + options.setContinuousProfiler(NoOpContinuousProfiler.getInstance()) + }) + val span = tracer.startChild("span.op") + assertNull(span.getData(SpanDataConvention.PROFILER_ID)) + } + @Test fun `when transaction is finished, transaction is cleared from the scope`() { val tracer = fixture.getSut() From 218bdebdacb8f809909efb187f446967a423e567 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 26 Sep 2024 11:18:57 +0000 Subject: [PATCH 04/10] Format code --- sentry/src/test/java/io/sentry/SentryTracerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index f6d6f8596d6..b11fae9e82d 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -1087,7 +1087,7 @@ class SentryTracerTest { @Test fun `when transaction is created, but not profiled, transactionPerformanceCollector is started anyway`() { val transaction = fixture.getSut() - verify(fixture.transactionPerformanceCollector).start(anyOrNull()) + verify(fixture.transactionPerformanceCollector).start(anyOrNull()) } @Test From a62b060bda288d00b2431f61028ef92954701976 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 27 Sep 2024 09:55:40 +0200 Subject: [PATCH 05/10] close continuous profiler on scopes close --- sentry/src/main/java/io/sentry/Scopes.java | 1 + sentry/src/test/java/io/sentry/ScopesTest.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 02bb33181a8..377161dffd6 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -411,6 +411,7 @@ public void close(final boolean isRestarting) { configureScope(scope -> scope.clear()); configureScope(ScopeType.ISOLATION, scope -> scope.clear()); getOptions().getTransactionProfiler().close(); + getOptions().getContinuousProfiler().close(); getOptions().getTransactionPerformanceCollector().close(); final @NotNull ISentryExecutorService executorService = getOptions().getExecutorService(); if (isRestarting) { diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index e06166fe4c0..5cd7effad38 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -1795,6 +1795,7 @@ class ScopesTest { fun `Scopes should close the sentry executor processor, profiler and performance collector on close call`() { val executor = mock() val profiler = mock() + val continuousProfiler = mock() val performanceCollector = mock() val options = SentryOptions().apply { dsn = "https://key@sentry.io/proj" @@ -1802,11 +1803,13 @@ class ScopesTest { executorService = executor setTransactionProfiler(profiler) transactionPerformanceCollector = performanceCollector + setContinuousProfiler(continuousProfiler) } val sut = createScopes(options) sut.close() verify(executor).close(any()) verify(profiler).close() + verify(continuousProfiler).close() verify(performanceCollector).close() } From d50f482516b5c52ae01d2468d2acf1cec00df0fb Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 12 Nov 2024 12:17:00 +0100 Subject: [PATCH 06/10] renamed TransactionPerformanceCollector to CompositePerformanceCollector --- .../core/AndroidContinuousProfiler.java | 6 +- .../core/AndroidOptionsInitializer.java | 4 +- .../core/AndroidContinuousProfilerTest.kt | 14 ++-- .../core/AndroidOptionsInitializerTest.kt | 6 +- sentry-android-ndk/build.gradle.kts | 1 + .../api/sentry-opentelemetry-extra.api | 2 +- .../sentry/opentelemetry/OtelSpanFactory.java | 4 +- sentry/api/sentry.api | 75 ++++++++++--------- ...ava => CompositePerformanceCollector.java} | 2 +- ...DefaultCompositePerformanceCollector.java} | 5 +- .../java/io/sentry/DefaultSpanFactory.java | 4 +- .../src/main/java/io/sentry/ISpanFactory.java | 2 +- ...=> NoOpCompositePerformanceCollector.java} | 10 +-- .../main/java/io/sentry/NoOpSpanFactory.java | 2 +- sentry/src/main/java/io/sentry/Scopes.java | 10 +-- .../main/java/io/sentry/SentryOptions.java | 20 ++--- .../src/main/java/io/sentry/SentryTracer.java | 26 +++---- ...faultCompositePerformanceCollectorTest.kt} | 8 +- sentry/src/test/java/io/sentry/ScopesTest.kt | 4 +- .../test/java/io/sentry/SentryOptionsTest.kt | 16 ++-- .../test/java/io/sentry/SentryTracerTest.kt | 30 ++++---- 21 files changed, 128 insertions(+), 123 deletions(-) rename sentry/src/main/java/io/sentry/{TransactionPerformanceCollector.java => CompositePerformanceCollector.java} (95%) rename sentry/src/main/java/io/sentry/{DefaultTransactionPerformanceCollector.java => DefaultCompositePerformanceCollector.java} (97%) rename sentry/src/main/java/io/sentry/{NoOpTransactionPerformanceCollector.java => NoOpCompositePerformanceCollector.java} (67%) rename sentry/src/test/java/io/sentry/{DefaultTransactionPerformanceCollectorTest.kt => DefaultCompositePerformanceCollectorTest.kt} (98%) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 5cf09ee20d2..a63e7b258cb 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.os.Build; +import io.sentry.CompositePerformanceCollector; import io.sentry.IContinuousProfiler; import io.sentry.ILogger; import io.sentry.IScopes; @@ -12,7 +13,6 @@ import io.sentry.ProfileChunk; import io.sentry.SentryLevel; import io.sentry.SentryOptions; -import io.sentry.TransactionPerformanceCollector; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.SentryId; import java.util.ArrayList; @@ -38,7 +38,7 @@ public class AndroidContinuousProfiler implements IContinuousProfiler { private boolean isRunning = false; private @Nullable IScopes scopes; private @Nullable Future stopFuture; - private @Nullable TransactionPerformanceCollector performanceCollector; + private @Nullable CompositePerformanceCollector performanceCollector; private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; @@ -90,7 +90,7 @@ private void init() { public synchronized void setScopes(final @NotNull IScopes scopes) { this.scopes = scopes; - this.performanceCollector = scopes.getOptions().getTransactionPerformanceCollector(); + this.performanceCollector = scopes.getOptions().getCompositePerformanceCollector(); } public synchronized void start() { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 86b5e1b355a..251424cd90a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -6,7 +6,7 @@ import android.content.Context; import android.content.pm.PackageInfo; import io.sentry.DeduplicateMultithreadedEventProcessor; -import io.sentry.DefaultTransactionPerformanceCollector; +import io.sentry.DefaultCompositePerformanceCollector; import io.sentry.ILogger; import io.sentry.ITransactionProfiler; import io.sentry.NoOpConnectionStatusProvider; @@ -245,7 +245,7 @@ static void initializeIntegrationsAndProcessors( "options.getFrameMetricsCollector is required"))); } } - options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options)); + options.setCompositePerformanceCollector(new DefaultCompositePerformanceCollector(options)); if (options.getCacheDirPath() != null) { if (options.isEnableScopePersistence()) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index ebad066e7db..d3d9266a5ac 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -4,6 +4,7 @@ import android.content.Context import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.CompositePerformanceCollector import io.sentry.CpuCollectionData import io.sentry.ILogger import io.sentry.IScopes @@ -14,7 +15,6 @@ import io.sentry.SentryLevel import io.sentry.SentryNanotimeDate import io.sentry.SentryTracer import io.sentry.TransactionContext -import io.sentry.TransactionPerformanceCollector import io.sentry.android.core.internal.util.SentryFrameMetricsCollector import io.sentry.profilemeasurements.ProfileMeasurement import io.sentry.test.DeferredExecutorService @@ -264,8 +264,8 @@ class AndroidContinuousProfilerTest { @Test fun `profiler starts performance collector on start`() { - val performanceCollector = mock() - fixture.options.transactionPerformanceCollector = performanceCollector + val performanceCollector = mock() + fixture.options.compositePerformanceCollector = performanceCollector val profiler = fixture.getSut() verify(performanceCollector, never()).start(any()) profiler.start() @@ -274,8 +274,8 @@ class AndroidContinuousProfilerTest { @Test fun `profiler stops performance collector on stop`() { - val performanceCollector = mock() - fixture.options.transactionPerformanceCollector = performanceCollector + val performanceCollector = mock() + fixture.options.compositePerformanceCollector = performanceCollector val profiler = fixture.getSut() profiler.start() verify(performanceCollector, never()).stop(any()) @@ -350,14 +350,14 @@ class AndroidContinuousProfilerTest { @Test fun `profiler sends chunk with measurements`() { val executorService = DeferredExecutorService() - val performanceCollector = mock() + val performanceCollector = mock() val collectionData = PerformanceCollectionData() collectionData.addMemoryData(MemoryCollectionData(1, 2, 3, SentryNanotimeDate())) collectionData.addCpuData(CpuCollectionData(1, 3.0, SentryNanotimeDate())) whenever(performanceCollector.stop(any())).thenReturn(listOf(collectionData)) - fixture.options.transactionPerformanceCollector = performanceCollector + fixture.options.compositePerformanceCollector = performanceCollector val profiler = fixture.getSut { it.executorService = executorService } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index e3a52902668..712afd85f69 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -6,7 +6,7 @@ import android.os.Build import android.os.Bundle import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.sentry.DefaultTransactionPerformanceCollector +import io.sentry.DefaultCompositePerformanceCollector import io.sentry.ILogger import io.sentry.MainEventProcessor import io.sentry.NoOpContinuousProfiler @@ -650,10 +650,10 @@ class AndroidOptionsInitializerTest { } @Test - fun `DefaultTransactionPerformanceCollector is set to options`() { + fun `DefaultCompositePerformanceCollector is set to options`() { fixture.initSut() - assertIs(fixture.sentryOptions.transactionPerformanceCollector) + assertIs(fixture.sentryOptions.compositePerformanceCollector) } @Test diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts index fe670631394..c8c8585b98a 100644 --- a/sentry-android-ndk/build.gradle.kts +++ b/sentry-android-ndk/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { api(projects.sentry) api(projects.sentryAndroidCore) + //noinspection GradleDependency implementation("io.sentry:sentry-native-ndk:0.7.5") compileOnly(Config.CompileOnly.jetbrainsAnnotations) diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api b/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api index e33a27d38bb..12bcb3c3b55 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api @@ -21,7 +21,7 @@ public final class io/sentry/opentelemetry/OtelSpanContext : io/sentry/SpanConte public final class io/sentry/opentelemetry/OtelSpanFactory : io/sentry/ISpanFactory { public fun ()V public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/ISpan { diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java index 7869f6dc963..19de213e181 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.sentry.Baggage; +import io.sentry.CompositePerformanceCollector; import io.sentry.IScopes; import io.sentry.ISpan; import io.sentry.ISpanFactory; @@ -21,7 +22,6 @@ import io.sentry.TracesSamplingDecision; import io.sentry.TransactionContext; import io.sentry.TransactionOptions; -import io.sentry.TransactionPerformanceCollector; import io.sentry.protocol.SentryId; import io.sentry.util.SpanUtils; import java.util.concurrent.TimeUnit; @@ -39,7 +39,7 @@ public final class OtelSpanFactory implements ISpanFactory { @NotNull TransactionContext context, @NotNull IScopes scopes, @NotNull TransactionOptions transactionOptions, - @Nullable TransactionPerformanceCollector transactionPerformanceCollector) { + @Nullable CompositePerformanceCollector compositePerformanceCollector) { final @Nullable OtelSpanWrapper span = createSpanInternal( scopes, transactionOptions, null, context.getSamplingDecision(), context); diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 93b5cb83c1e..156ba883288 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -307,6 +307,16 @@ public final class io/sentry/CombinedScopeView : io/sentry/IScope { public fun withTransaction (Lio/sentry/Scope$IWithTransaction;)V } +public abstract interface class io/sentry/CompositePerformanceCollector { + public abstract fun close ()V + public abstract fun onSpanFinished (Lio/sentry/ISpan;)V + public abstract fun onSpanStarted (Lio/sentry/ISpan;)V + public abstract fun start (Lio/sentry/ITransaction;)V + public abstract fun start (Ljava/lang/String;)V + public abstract fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public abstract fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/CpuCollectionData { public fun (JDLio/sentry/SentryDate;)V public fun getCpuUsagePercentage ()D @@ -365,6 +375,17 @@ public final class io/sentry/DeduplicateMultithreadedEventProcessor : io/sentry/ public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; } +public final class io/sentry/DefaultCompositePerformanceCollector : io/sentry/CompositePerformanceCollector { + public fun (Lio/sentry/SentryOptions;)V + public fun close ()V + public fun onSpanFinished (Lio/sentry/ISpan;)V + public fun onSpanStarted (Lio/sentry/ISpan;)V + public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V + public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/DefaultScopesStorage : io/sentry/IScopesStorage { public fun ()V public fun close ()V @@ -375,18 +396,7 @@ public final class io/sentry/DefaultScopesStorage : io/sentry/IScopesStorage { public final class io/sentry/DefaultSpanFactory : io/sentry/ISpanFactory { public fun ()V public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; -} - -public final class io/sentry/DefaultTransactionPerformanceCollector : io/sentry/TransactionPerformanceCollector { - public fun (Lio/sentry/SentryOptions;)V - public fun close ()V - public fun onSpanFinished (Lio/sentry/ISpan;)V - public fun onSpanStarted (Lio/sentry/ISpan;)V - public fun start (Lio/sentry/ITransaction;)V - public fun start (Ljava/lang/String;)V - public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; - public fun stop (Ljava/lang/String;)Ljava/util/List; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public final class io/sentry/DiagnosticLogger : io/sentry/ILogger { @@ -1039,7 +1049,7 @@ public abstract interface class io/sentry/ISpan { public abstract interface class io/sentry/ISpanFactory { public abstract fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public abstract fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public abstract fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public abstract interface class io/sentry/ITransaction : io/sentry/ISpan { @@ -1393,6 +1403,17 @@ public final class io/sentry/MonitorScheduleUnit : java/lang/Enum { public static fun values ()[Lio/sentry/MonitorScheduleUnit; } +public final class io/sentry/NoOpCompositePerformanceCollector : io/sentry/CompositePerformanceCollector { + public fun close ()V + public static fun getInstance ()Lio/sentry/NoOpCompositePerformanceCollector; + public fun onSpanFinished (Lio/sentry/ISpan;)V + public fun onSpanStarted (Lio/sentry/ISpan;)V + public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V + public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/NoOpConnectionStatusProvider : io/sentry/IConnectionStatusProvider { public fun ()V public fun addConnectionStatusObserver (Lio/sentry/IConnectionStatusProvider$IConnectionStatusObserver;)Z @@ -1702,7 +1723,7 @@ public final class io/sentry/NoOpSpan : io/sentry/ISpan { public final class io/sentry/NoOpSpanFactory : io/sentry/ISpanFactory { public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; public static fun getInstance ()Lio/sentry/NoOpSpanFactory; } @@ -1760,17 +1781,6 @@ public final class io/sentry/NoOpTransaction : io/sentry/ITransaction { public fun updateEndDate (Lio/sentry/SentryDate;)Z } -public final class io/sentry/NoOpTransactionPerformanceCollector : io/sentry/TransactionPerformanceCollector { - public fun close ()V - public static fun getInstance ()Lio/sentry/NoOpTransactionPerformanceCollector; - public fun onSpanFinished (Lio/sentry/ISpan;)V - public fun onSpanStarted (Lio/sentry/ISpan;)V - public fun start (Lio/sentry/ITransaction;)V - public fun start (Ljava/lang/String;)V - public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; - public fun stop (Ljava/lang/String;)Ljava/util/List; -} - public final class io/sentry/NoOpTransactionProfiler : io/sentry/ITransactionProfiler { public fun bindTransaction (Lio/sentry/ITransaction;)V public fun close ()V @@ -2952,6 +2962,7 @@ public class io/sentry/SentryOptions { public fun getBundleIds ()Ljava/util/Set; public fun getCacheDirPath ()Ljava/lang/String; public fun getClientReportRecorder ()Lio/sentry/clientreport/IClientReportRecorder; + public fun getCompositePerformanceCollector ()Lio/sentry/CompositePerformanceCollector; public fun getConnectionStatusProvider ()Lio/sentry/IConnectionStatusProvider; public fun getConnectionTimeoutMillis ()I public fun getContextTags ()Ljava/util/List; @@ -3025,7 +3036,6 @@ public class io/sentry/SentryOptions { public fun getTracesSampleRate ()Ljava/lang/Double; public fun getTracesSampler ()Lio/sentry/SentryOptions$TracesSamplerCallback; public fun getTracingOrigins ()Ljava/util/List; - public fun getTransactionPerformanceCollector ()Lio/sentry/TransactionPerformanceCollector; public fun getTransactionProfiler ()Lio/sentry/ITransactionProfiler; public fun getTransportFactory ()Lio/sentry/ITransportFactory; public fun getTransportGate ()Lio/sentry/transport/ITransportGate; @@ -3072,6 +3082,7 @@ public class io/sentry/SentryOptions { public fun setBeforeSend (Lio/sentry/SentryOptions$BeforeSendCallback;)V public fun setBeforeSendTransaction (Lio/sentry/SentryOptions$BeforeSendTransactionCallback;)V public fun setCacheDirPath (Ljava/lang/String;)V + public fun setCompositePerformanceCollector (Lio/sentry/CompositePerformanceCollector;)V public fun setConnectionStatusProvider (Lio/sentry/IConnectionStatusProvider;)V public fun setConnectionTimeoutMillis (I)V public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V @@ -3158,7 +3169,6 @@ public class io/sentry/SentryOptions { public fun setTracesSampleRate (Ljava/lang/Double;)V public fun setTracesSampler (Lio/sentry/SentryOptions$TracesSamplerCallback;)V public fun setTracingOrigins (Ljava/util/List;)V - public fun setTransactionPerformanceCollector (Lio/sentry/TransactionPerformanceCollector;)V public fun setTransactionProfiler (Lio/sentry/ITransactionProfiler;)V public fun setTransportFactory (Lio/sentry/ITransportFactory;)V public fun setTransportGate (Lio/sentry/transport/ITransportGate;)V @@ -3634,6 +3644,7 @@ public abstract interface class io/sentry/SpanDataConvention { public static final field HTTP_RESPONSE_CONTENT_LENGTH_KEY Ljava/lang/String; public static final field HTTP_START_TIMESTAMP Ljava/lang/String; public static final field HTTP_STATUS_CODE_KEY Ljava/lang/String; + public static final field PROFILER_ID Ljava/lang/String; public static final field THREAD_ID Ljava/lang/String; public static final field THREAD_NAME Ljava/lang/String; } @@ -3819,16 +3830,6 @@ public final class io/sentry/TransactionOptions : io/sentry/SpanOptions { public fun setWaitForChildren (Z)V } -public abstract interface class io/sentry/TransactionPerformanceCollector { - public abstract fun close ()V - public abstract fun onSpanFinished (Lio/sentry/ISpan;)V - public abstract fun onSpanStarted (Lio/sentry/ISpan;)V - public abstract fun start (Lio/sentry/ITransaction;)V - public abstract fun start (Ljava/lang/String;)V - public abstract fun stop (Lio/sentry/ITransaction;)Ljava/util/List; - public abstract fun stop (Ljava/lang/String;)Ljava/util/List; -} - public final class io/sentry/TypeCheckHint { public static final field ANDROID_ACTIVITY Ljava/lang/String; public static final field ANDROID_CONFIGURATION Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/CompositePerformanceCollector.java similarity index 95% rename from sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/CompositePerformanceCollector.java index 300e771b5df..e6238679a79 100644 --- a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/CompositePerformanceCollector.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public interface TransactionPerformanceCollector { +public interface CompositePerformanceCollector { /** Starts collecting performance data and span related data (e.g. slow/frozen frames). */ void start(@NotNull ITransaction transaction); diff --git a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java similarity index 97% rename from sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java index 53efe29ee60..37f081ec07f 100644 --- a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java @@ -14,8 +14,7 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Internal -public final class DefaultTransactionPerformanceCollector - implements TransactionPerformanceCollector { +public final class DefaultCompositePerformanceCollector implements CompositePerformanceCollector { private static final long TRANSACTION_COLLECTION_INTERVAL_MILLIS = 100; private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000; private final @NotNull Object timerLock = new Object(); @@ -30,7 +29,7 @@ public final class DefaultTransactionPerformanceCollector private final @NotNull AtomicBoolean isStarted = new AtomicBoolean(false); private long lastCollectionTimestamp = 0; - public DefaultTransactionPerformanceCollector(final @NotNull SentryOptions options) { + public DefaultCompositePerformanceCollector(final @NotNull SentryOptions options) { this.options = Objects.requireNonNull(options, "The options object is required."); this.snapshotCollectors = new ArrayList<>(); this.continuousCollectors = new ArrayList<>(); diff --git a/sentry/src/main/java/io/sentry/DefaultSpanFactory.java b/sentry/src/main/java/io/sentry/DefaultSpanFactory.java index 7ac24488499..6054ed51667 100644 --- a/sentry/src/main/java/io/sentry/DefaultSpanFactory.java +++ b/sentry/src/main/java/io/sentry/DefaultSpanFactory.java @@ -11,8 +11,8 @@ public final class DefaultSpanFactory implements ISpanFactory { final @NotNull TransactionContext context, final @NotNull IScopes scopes, final @NotNull TransactionOptions transactionOptions, - final @Nullable TransactionPerformanceCollector transactionPerformanceCollector) { - return new SentryTracer(context, scopes, transactionOptions, transactionPerformanceCollector); + final @Nullable CompositePerformanceCollector compositePerformanceCollector) { + return new SentryTracer(context, scopes, transactionOptions, compositePerformanceCollector); } @Override diff --git a/sentry/src/main/java/io/sentry/ISpanFactory.java b/sentry/src/main/java/io/sentry/ISpanFactory.java index 1e429e2fea4..9b7c6afabab 100644 --- a/sentry/src/main/java/io/sentry/ISpanFactory.java +++ b/sentry/src/main/java/io/sentry/ISpanFactory.java @@ -11,7 +11,7 @@ ITransaction createTransaction( @NotNull TransactionContext context, @NotNull IScopes scopes, @NotNull TransactionOptions transactionOptions, - @Nullable TransactionPerformanceCollector transactionPerformanceCollector); + @Nullable CompositePerformanceCollector compositePerformanceCollector); @NotNull ISpan createSpan( diff --git a/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/NoOpCompositePerformanceCollector.java similarity index 67% rename from sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/NoOpCompositePerformanceCollector.java index 91533069d71..a159be9182f 100644 --- a/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/NoOpCompositePerformanceCollector.java @@ -4,16 +4,16 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class NoOpTransactionPerformanceCollector implements TransactionPerformanceCollector { +public final class NoOpCompositePerformanceCollector implements CompositePerformanceCollector { - private static final NoOpTransactionPerformanceCollector instance = - new NoOpTransactionPerformanceCollector(); + private static final NoOpCompositePerformanceCollector instance = + new NoOpCompositePerformanceCollector(); - public static NoOpTransactionPerformanceCollector getInstance() { + public static NoOpCompositePerformanceCollector getInstance() { return instance; } - private NoOpTransactionPerformanceCollector() {} + private NoOpCompositePerformanceCollector() {} @Override public void start(@NotNull ITransaction transaction) {} diff --git a/sentry/src/main/java/io/sentry/NoOpSpanFactory.java b/sentry/src/main/java/io/sentry/NoOpSpanFactory.java index 05bea4edfe9..871e2810542 100644 --- a/sentry/src/main/java/io/sentry/NoOpSpanFactory.java +++ b/sentry/src/main/java/io/sentry/NoOpSpanFactory.java @@ -20,7 +20,7 @@ public static NoOpSpanFactory getInstance() { @NotNull TransactionContext context, @NotNull IScopes scopes, @NotNull TransactionOptions transactionOptions, - @Nullable TransactionPerformanceCollector transactionPerformanceCollector) { + @Nullable CompositePerformanceCollector compositePerformanceCollector) { return NoOpTransaction.getInstance(); } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 377161dffd6..80355d7678f 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -31,7 +31,7 @@ public final class Scopes implements IScopes, MetricsApi.IMetricsInterface { private final @Nullable Scopes parentScopes; private final @NotNull String creator; - private final @NotNull TransactionPerformanceCollector transactionPerformanceCollector; + private final @NotNull CompositePerformanceCollector compositePerformanceCollector; private final @NotNull MetricsApi metricsApi; private final @NotNull CombinedScopeView combinedScope; @@ -59,7 +59,7 @@ private Scopes( final @NotNull SentryOptions options = getOptions(); validateOptions(options); - this.transactionPerformanceCollector = options.getTransactionPerformanceCollector(); + this.compositePerformanceCollector = options.getCompositePerformanceCollector(); this.metricsApi = new MetricsApi(this); } @@ -412,7 +412,7 @@ public void close(final boolean isRestarting) { configureScope(ScopeType.ISOLATION, scope -> scope.clear()); getOptions().getTransactionProfiler().close(); getOptions().getContinuousProfiler().close(); - getOptions().getTransactionPerformanceCollector().close(); + getOptions().getCompositePerformanceCollector().close(); final @NotNull ISentryExecutorService executorService = getOptions().getExecutorService(); if (isRestarting) { executorService.submit( @@ -905,10 +905,10 @@ public void flush(long timeoutMillis) { transaction = spanFactory.createTransaction( - transactionContext, this, transactionOptions, transactionPerformanceCollector); + transactionContext, this, transactionOptions, compositePerformanceCollector); // new SentryTracer( // transactionContext, this, transactionOptions, - // transactionPerformanceCollector); + // compositePerformanceCollector); // The listener is called only if the transaction exists, as the transaction is needed to // stop it diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index c7a7d41d78a..dce4f3dc89b 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -425,8 +425,8 @@ public class SentryOptions { private final @NotNull List performanceCollectors = new ArrayList<>(); /** Performance collector that collect performance stats while transactions run. */ - private @NotNull TransactionPerformanceCollector transactionPerformanceCollector = - NoOpTransactionPerformanceCollector.getInstance(); + private @NotNull CompositePerformanceCollector compositePerformanceCollector = + NoOpCompositePerformanceCollector.getInstance(); /** Enables the time-to-full-display spans in navigation transactions. */ private boolean enableTimeToFullDisplayTracing = false; @@ -2103,24 +2103,24 @@ public void setThreadChecker(final @NotNull IThreadChecker threadChecker) { } /** - * Gets the performance collector used to collect performance stats while transactions run. + * Gets the performance collector used to collect performance stats in a time period. * * @return the performance collector. */ @ApiStatus.Internal - public @NotNull TransactionPerformanceCollector getTransactionPerformanceCollector() { - return transactionPerformanceCollector; + public @NotNull CompositePerformanceCollector getCompositePerformanceCollector() { + return compositePerformanceCollector; } /** - * Sets the performance collector used to collect performance stats while transactions run. + * Sets the performance collector used to collect performance stats in a time period. * - * @param transactionPerformanceCollector the performance collector. + * @param compositePerformanceCollector the performance collector. */ @ApiStatus.Internal - public void setTransactionPerformanceCollector( - final @NotNull TransactionPerformanceCollector transactionPerformanceCollector) { - this.transactionPerformanceCollector = transactionPerformanceCollector; + public void setCompositePerformanceCollector( + final @NotNull CompositePerformanceCollector compositePerformanceCollector) { + this.compositePerformanceCollector = compositePerformanceCollector; } /** diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index d2956ba9b10..972dd9351cb 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -49,7 +49,7 @@ public final class SentryTracer implements ITransaction { private @NotNull TransactionNameSource transactionNameSource; private final @NotNull Instrumenter instrumenter; private final @NotNull Contexts contexts = new Contexts(); - private final @Nullable TransactionPerformanceCollector transactionPerformanceCollector; + private final @Nullable CompositePerformanceCollector compositePerformanceCollector; private final @NotNull TransactionOptions transactionOptions; public SentryTracer(final @NotNull TransactionContext context, final @NotNull IScopes scopes) { @@ -67,7 +67,7 @@ public SentryTracer( final @NotNull TransactionContext context, final @NotNull IScopes scopes, final @NotNull TransactionOptions transactionOptions, - final @Nullable TransactionPerformanceCollector transactionPerformanceCollector) { + final @Nullable CompositePerformanceCollector compositePerformanceCollector) { Objects.requireNonNull(context, "context is required"); Objects.requireNonNull(scopes, "scopes are required"); @@ -76,7 +76,7 @@ public SentryTracer( this.name = context.getName(); this.instrumenter = context.getInstrumenter(); this.scopes = scopes; - this.transactionPerformanceCollector = transactionPerformanceCollector; + this.compositePerformanceCollector = compositePerformanceCollector; this.transactionNameSource = context.getTransactionNameSource(); this.transactionOptions = transactionOptions; @@ -94,8 +94,8 @@ public SentryTracer( // We are currently sending the performance data only in profiles, but we are always sending // performance measurements. - if (transactionPerformanceCollector != null) { - transactionPerformanceCollector.start(this); + if (compositePerformanceCollector != null) { + compositePerformanceCollector.start(this); } if (transactionOptions.getIdleTimeout() != null @@ -225,8 +225,8 @@ public void finish( finishedCallback.execute(this); } - if (transactionPerformanceCollector != null) { - performanceCollectionData.set(transactionPerformanceCollector.stop(this)); + if (compositePerformanceCollector != null) { + performanceCollectionData.set(compositePerformanceCollector.stop(this)); } }); @@ -474,8 +474,8 @@ private ISpan createChild( spanContext, spanOptions, finishingSpan -> { - if (transactionPerformanceCollector != null) { - transactionPerformanceCollector.onSpanFinished(finishingSpan); + if (compositePerformanceCollector != null) { + compositePerformanceCollector.onSpanFinished(finishingSpan); } final FinishStatus finishStatus = this.finishStatus; if (transactionOptions.getIdleTimeout() != null) { @@ -500,8 +500,8 @@ private ISpan createChild( // timestamp, // spanOptions, // finishingSpan -> { - // if (transactionPerformanceCollector != null) { - // transactionPerformanceCollector.onSpanFinished(finishingSpan); + // if (compositePerformanceCollector != null) { + // compositePerformanceCollector.onSpanFinished(finishingSpan); // } // final FinishStatus finishStatus = this.finishStatus; // if (transactionOptions.getIdleTimeout() != null) { @@ -530,8 +530,8 @@ private ISpan createChild( ? "main" : Thread.currentThread().getName()); this.children.add(span); - if (transactionPerformanceCollector != null) { - transactionPerformanceCollector.onSpanStarted(span); + if (compositePerformanceCollector != null) { + compositePerformanceCollector.onSpanStarted(span); } return span; } else { diff --git a/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt b/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt similarity index 98% rename from sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt rename to sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt index 1a8f3bfc04e..fe9dd6039dc 100644 --- a/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt +++ b/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt @@ -23,9 +23,9 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue -class DefaultTransactionPerformanceCollectorTest { +class DefaultCompositePerformanceCollectorTest { - private val className = "io.sentry.DefaultTransactionPerformanceCollector" + private val className = "io.sentry.DefaultCompositePerformanceCollector" private val ctorTypes: Array> = arrayOf(SentryOptions::class.java) private val fixture = Fixture() private val threadChecker = ThreadChecker.getInstance() @@ -51,7 +51,7 @@ class DefaultTransactionPerformanceCollectorTest { whenever(scopes.options).thenReturn(options) } - fun getSut(memoryCollector: IPerformanceSnapshotCollector? = JavaMemoryCollector(), cpuCollector: IPerformanceSnapshotCollector? = mockCpuCollector, executorService: ISentryExecutorService = deferredExecutorService): TransactionPerformanceCollector { + fun getSut(memoryCollector: IPerformanceSnapshotCollector? = JavaMemoryCollector(), cpuCollector: IPerformanceSnapshotCollector? = mockCpuCollector, executorService: ISentryExecutorService = deferredExecutorService): CompositePerformanceCollector { options.dsn = "https://key@sentry.io/proj" options.executorService = executorService if (cpuCollector != null) { @@ -62,7 +62,7 @@ class DefaultTransactionPerformanceCollectorTest { } transaction1 = SentryTracer(TransactionContext("", ""), scopes) transaction2 = SentryTracer(TransactionContext("", ""), scopes) - val collector = DefaultTransactionPerformanceCollector(options) + val collector = DefaultCompositePerformanceCollector(options) val timer: Timer = collector.getProperty("timer") ?: Timer(true) mockTimer = spy(timer) collector.injectForField("timer", mockTimer) diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 5cd7effad38..e176d9b68c8 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -1796,13 +1796,13 @@ class ScopesTest { val executor = mock() val profiler = mock() val continuousProfiler = mock() - val performanceCollector = mock() + val performanceCollector = mock() val options = SentryOptions().apply { dsn = "https://key@sentry.io/proj" cacheDirPath = file.absolutePath executorService = executor setTransactionProfiler(profiler) - transactionPerformanceCollector = performanceCollector + compositePerformanceCollector = performanceCollector setContinuousProfiler(continuousProfiler) } val sut = createScopes(options) diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 6bae6602764..51bac284d6b 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -295,8 +295,8 @@ class SentryOptionsTest { } @Test - fun `when options is initialized, transactionPerformanceCollector is set`() { - assertIs(SentryOptions().transactionPerformanceCollector) + fun `when options is initialized, compositePerformanceCollector is set`() { + assertIs(SentryOptions().compositePerformanceCollector) } @Test @@ -516,16 +516,16 @@ class SentryOptionsTest { } @Test - fun `when options are initialized, TransactionPerformanceCollector is a NoOp`() { - assertEquals(SentryOptions().transactionPerformanceCollector, NoOpTransactionPerformanceCollector.getInstance()) + fun `when options are initialized, CompositePerformanceCollector is a NoOp`() { + assertEquals(SentryOptions().compositePerformanceCollector, NoOpCompositePerformanceCollector.getInstance()) } @Test - fun `when setTransactionPerformanceCollector is called, overrides default`() { - val performanceCollector = mock() + fun `when setCompositePerformanceCollector is called, overrides default`() { + val performanceCollector = mock() val options = SentryOptions() - options.transactionPerformanceCollector = performanceCollector - assertEquals(performanceCollector, options.transactionPerformanceCollector) + options.compositePerformanceCollector = performanceCollector + assertEquals(performanceCollector, options.compositePerformanceCollector) } @Test diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index b11fae9e82d..1ede9007f47 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -32,14 +32,18 @@ class SentryTracerTest { private class Fixture { val options = SentryOptions() val scopes: Scopes - val transactionPerformanceCollector: TransactionPerformanceCollector + val compositePerformanceCollector: CompositePerformanceCollector init { options.dsn = "https://key@sentry.io/proj" options.environment = "environment" options.release = "release@3.0.0" scopes = spy(createTestScopes(options)) - transactionPerformanceCollector = spy(DefaultTransactionPerformanceCollector(options)) + compositePerformanceCollector = spy( + DefaultCompositePerformanceCollector( + options + ) + ) } fun getSut( @@ -51,7 +55,7 @@ class SentryTracerTest { trimEnd: Boolean = false, transactionFinishedCallback: TransactionFinishedCallback? = null, samplingDecision: TracesSamplingDecision? = null, - performanceCollector: TransactionPerformanceCollector? = transactionPerformanceCollector + performanceCollector: CompositePerformanceCollector? = compositePerformanceCollector ): SentryTracer { optionsConfiguration.configure(options) @@ -1085,35 +1089,35 @@ class SentryTracerTest { } @Test - fun `when transaction is created, but not profiled, transactionPerformanceCollector is started anyway`() { + fun `when transaction is created, but not profiled, compositePerformanceCollector is started anyway`() { val transaction = fixture.getSut() - verify(fixture.transactionPerformanceCollector).start(anyOrNull()) + verify(fixture.compositePerformanceCollector).start(anyOrNull()) } @Test - fun `when transaction is created and profiled transactionPerformanceCollector is started`() { + fun `when transaction is created and profiled compositePerformanceCollector is started`() { val transaction = fixture.getSut(optionsConfiguration = { it.profilesSampleRate = 1.0 }, samplingDecision = TracesSamplingDecision(true, null, true, null)) - verify(fixture.transactionPerformanceCollector).start(check { assertEquals(transaction, it) }) + verify(fixture.compositePerformanceCollector).start(check { assertEquals(transaction, it) }) } @Test - fun `when transaction is finished, transactionPerformanceCollector is stopped`() { + fun `when transaction is finished, compositePerformanceCollector is stopped`() { val transaction = fixture.getSut() transaction.finish() - verify(fixture.transactionPerformanceCollector).stop(check { assertEquals(transaction, it) }) + verify(fixture.compositePerformanceCollector).stop(check { assertEquals(transaction, it) }) } @Test - fun `when a span is started and finished the transactionPerformanceCollector gets notified`() { + fun `when a span is started and finished the compositePerformanceCollector gets notified`() { val transaction = fixture.getSut() val span = transaction.startChild("op.span") span.finish() - verify(fixture.transactionPerformanceCollector).onSpanStarted(check { assertEquals(span, it) }) - verify(fixture.transactionPerformanceCollector).onSpanFinished(check { assertEquals(span, it) }) + verify(fixture.compositePerformanceCollector).onSpanStarted(check { assertEquals(span, it) }) + verify(fixture.compositePerformanceCollector).onSpanFinished(check { assertEquals(span, it) }) } @Test @@ -1267,7 +1271,7 @@ class SentryTracerTest { @Test fun `when transaction is finished, collected performance data is cleared`() { val data = mutableListOf(mock(), mock()) - val mockPerformanceCollector = object : TransactionPerformanceCollector { + val mockPerformanceCollector = object : CompositePerformanceCollector { override fun start(transaction: ITransaction) {} override fun start(id: String) {} override fun onSpanStarted(span: ISpan) {} From e84e4d459a82307e6b5d7c4c06da0bb38e3440fe Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 12 Nov 2024 17:47:35 +0100 Subject: [PATCH 07/10] added SpanContext.data ser/deser --- .../internal/util/AndroidThreadChecker.java | 5 ++++ .../internal/util/AndroidThreadCheckerTest.kt | 20 ++++++++++++++++ sentry-android-ndk/build.gradle.kts | 3 +-- .../android/sqlite/SQLiteSpanManagerTest.kt | 2 ++ .../webflux/SentryWebFluxTracingFilterTest.kt | 2 +- .../webflux/SentryWebFluxTracingFilterTest.kt | 2 +- sentry/api/sentry.api | 5 ++++ .../src/main/java/io/sentry/SentryTracer.java | 10 ++++---- .../src/main/java/io/sentry/SpanContext.java | 24 +++++++++++++++---- .../io/sentry/protocol/SentryTransaction.java | 6 +++-- .../io/sentry/util/thread/IThreadChecker.java | 8 +++++++ .../sentry/util/thread/NoOpThreadChecker.java | 5 ++++ .../io/sentry/util/thread/ThreadChecker.java | 5 ++++ .../io/sentry/CheckInSerializationTest.kt | 5 +++- .../test/java/io/sentry/JsonSerializerTest.kt | 10 +++++++- sentry/src/test/java/io/sentry/SentryTest.kt | 1 + .../test/java/io/sentry/SentryTracerTest.kt | 2 ++ .../protocol/SpanContextSerializationTest.kt | 3 +++ .../sentry/util/thread/ThreadCheckerTest.kt | 8 +++++++ .../test/resources/json/checkin_crontab.json | 7 +++++- .../test/resources/json/checkin_interval.json | 7 +++++- sentry/src/test/resources/json/contexts.json | 5 ++++ .../resources/json/sentry_base_event.json | 5 ++++ .../sentry_base_event_with_null_extra.json | 5 ++++ .../src/test/resources/json/sentry_event.json | 5 ++++ .../resources/json/sentry_replay_event.json | 5 ++++ .../resources/json/sentry_transaction.json | 5 ++++ ...sentry_transaction_legacy_date_format.json | 5 ++++ ...entry_transaction_no_measurement_unit.json | 5 ++++ .../src/test/resources/json/span_context.json | 5 ++++ 30 files changed, 165 insertions(+), 20 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java index 15781d711fb..ccd4a92b276 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java @@ -39,6 +39,11 @@ public boolean isMainThread() { return isMainThread(Thread.currentThread()); } + @Override + public @NotNull String getCurrentThreadName() { + return isMainThread() ? "main" : Thread.currentThread().getName(); + } + @Override public boolean isMainThread(final @NotNull SentryThread sentryThread) { final Long threadId = sentryThread.getId(); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidThreadCheckerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidThreadCheckerTest.kt index eb59f0732e1..0b3729f8ce4 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidThreadCheckerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidThreadCheckerTest.kt @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.protocol.SentryThread import org.junit.runner.RunWith import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -44,4 +45,23 @@ class AndroidThreadCheckerTest { } assertFalse(AndroidThreadChecker.getInstance().isMainThread(sentryThread)) } + + @Test + fun `currentThreadName returns main when called on the main thread`() { + val thread = Thread.currentThread() + thread.name = "test" + assertEquals("main", AndroidThreadChecker.getInstance().currentThreadName) + } + + @Test + fun `currentThreadName returns the name of the current thread`() { + var threadName = "" + val thread = Thread { + threadName = AndroidThreadChecker.getInstance().currentThreadName + } + thread.name = "test" + thread.start() + thread.join() + assertEquals("test", threadName) + } } diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts index c8c8585b98a..26a3003d87d 100644 --- a/sentry-android-ndk/build.gradle.kts +++ b/sentry-android-ndk/build.gradle.kts @@ -71,8 +71,7 @@ dependencies { api(projects.sentry) api(projects.sentryAndroidCore) - //noinspection GradleDependency - implementation("io.sentry:sentry-native-ndk:0.7.5") + implementation("io.sentry:sentry-native-ndk:0.7.8") compileOnly(Config.CompileOnly.jetbrainsAnnotations) diff --git a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/SQLiteSpanManagerTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/SQLiteSpanManagerTest.kt index 17c37d69bfc..b292f0d038f 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/SQLiteSpanManagerTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/SQLiteSpanManagerTest.kt @@ -100,6 +100,7 @@ class SQLiteSpanManagerTest { fixture.options.threadChecker = mock() whenever(fixture.options.threadChecker.isMainThread).thenReturn(false) + whenever(fixture.options.threadChecker.currentThreadName).thenReturn("test") sut.performSql("sql") {} val span = fixture.sentryTracer.children.first() @@ -114,6 +115,7 @@ class SQLiteSpanManagerTest { fixture.options.threadChecker = mock() whenever(fixture.options.threadChecker.isMainThread).thenReturn(true) + whenever(fixture.options.threadChecker.currentThreadName).thenReturn("test") sut.performSql("sql") {} val span = fixture.sentryTracer.children.first() diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt index 2b18b386515..3c2dba775ec 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/SentryWebFluxTracingFilterTest.kt @@ -248,7 +248,7 @@ class SentryWebFluxTracingFilterTest { verify(fixture.chain).filter(fixture.exchange) verify(fixture.scopes, times(2)).isEnabled - verify(fixture.scopes, times(2)).options + verify(fixture.scopes, times(3)).options verify(fixture.scopes).continueTrace(anyOrNull(), anyOrNull()) verify(fixture.scopes).addBreadcrumb(any(), any()) verify(fixture.scopes).configureScope(any()) diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt index 0b0d7e6496a..f502680a8fa 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/webflux/SentryWebFluxTracingFilterTest.kt @@ -249,7 +249,7 @@ class SentryWebFluxTracingFilterTest { verify(fixture.chain).filter(fixture.exchange) verify(fixture.scopes).isEnabled - verify(fixture.scopes, times(2)).options + verify(fixture.scopes, times(3)).options verify(fixture.scopes).continueTrace(anyOrNull(), anyOrNull()) verify(fixture.scopes).addBreadcrumb(any(), any()) verify(fixture.scopes).configureScope(any()) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 156ba883288..5a5be4cf7f4 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3581,6 +3581,7 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU public fun copyForChild (Ljava/lang/String;Lio/sentry/SpanId;Lio/sentry/SpanId;)Lio/sentry/SpanContext; public fun equals (Ljava/lang/Object;)Z public fun getBaggage ()Lio/sentry/Baggage; + public fun getData ()Ljava/util/Map; public fun getDescription ()Ljava/lang/String; public fun getInstrumenter ()Lio/sentry/Instrumenter; public fun getOperation ()Ljava/lang/String; @@ -3615,6 +3616,7 @@ public final class io/sentry/SpanContext$Deserializer : io/sentry/JsonDeserializ } public final class io/sentry/SpanContext$JsonKeys { + public static final field DATA Ljava/lang/String; public static final field DESCRIPTION Ljava/lang/String; public static final field OP Ljava/lang/String; public static final field ORIGIN Ljava/lang/String; @@ -6542,6 +6544,7 @@ public final class io/sentry/util/UrlUtils$UrlDetails { public abstract interface class io/sentry/util/thread/IThreadChecker { public abstract fun currentThreadSystemId ()J + public abstract fun getCurrentThreadName ()Ljava/lang/String; public abstract fun isMainThread ()Z public abstract fun isMainThread (J)Z public abstract fun isMainThread (Lio/sentry/protocol/SentryThread;)Z @@ -6551,6 +6554,7 @@ public abstract interface class io/sentry/util/thread/IThreadChecker { public final class io/sentry/util/thread/NoOpThreadChecker : io/sentry/util/thread/IThreadChecker { public fun ()V public fun currentThreadSystemId ()J + public fun getCurrentThreadName ()Ljava/lang/String; public static fun getInstance ()Lio/sentry/util/thread/NoOpThreadChecker; public fun isMainThread ()Z public fun isMainThread (J)Z @@ -6560,6 +6564,7 @@ public final class io/sentry/util/thread/NoOpThreadChecker : io/sentry/util/thre public final class io/sentry/util/thread/ThreadChecker : io/sentry/util/thread/IThreadChecker { public fun currentThreadSystemId ()J + public fun getCurrentThreadName ()Ljava/lang/String; public static fun getInstance ()Lio/sentry/util/thread/ThreadChecker; public fun isMainThread ()Z public fun isMainThread (J)Z diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 972dd9351cb..76c1c179d29 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -7,6 +7,7 @@ import io.sentry.protocol.TransactionNameSource; import io.sentry.util.Objects; import io.sentry.util.SpanUtils; +import io.sentry.util.thread.IThreadChecker; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; @@ -518,17 +519,14 @@ private ISpan createChild( // } // }); // span.setDescription(description); - final long threadId = scopes.getOptions().getThreadChecker().currentThreadSystemId(); + final @NotNull IThreadChecker threadChecker = scopes.getOptions().getThreadChecker(); final SentryId profilerId = scopes.getOptions().getContinuousProfiler().getProfilerId(); if (!profilerId.equals(SentryId.EMPTY_ID)) { span.setData(SpanDataConvention.PROFILER_ID, profilerId.toString()); } - span.setData(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); span.setData( - SpanDataConvention.THREAD_NAME, - scopes.getOptions().getThreadChecker().isMainThread() - ? "main" - : Thread.currentThread().getName()); + SpanDataConvention.THREAD_ID, String.valueOf(threadChecker.currentThreadSystemId())); + span.setData(SpanDataConvention.THREAD_NAME, threadChecker.getCurrentThreadName()); this.children.add(span); if (compositePerformanceCollector != null) { compositePerformanceCollector.onSpanStarted(span); diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index dcd8b9bebec..19479ac9e59 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -1,9 +1,11 @@ package io.sentry; import com.jakewharton.nopen.annotation.Open; +import io.sentry.protocol.Request; import io.sentry.protocol.SentryId; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; +import io.sentry.util.thread.IThreadChecker; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.Map; @@ -96,11 +98,12 @@ public SpanContext( this.description = description; this.status = status; this.origin = origin; - final long threadId = - ScopesAdapter.getInstance().getOptions().getThreadChecker().currentThreadSystemId(); + final IThreadChecker threadChecker = + ScopesAdapter.getInstance().getOptions().getThreadChecker(); this.data = new ConcurrentHashMap<>(); - this.data.put(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); - this.data.put(SpanDataConvention.THREAD_NAME, Thread.currentThread().getName()); + this.data.put( + SpanDataConvention.THREAD_ID, String.valueOf(threadChecker.currentThreadSystemId())); + this.data.put(SpanDataConvention.THREAD_NAME, threadChecker.getCurrentThreadName()); } /** @@ -235,6 +238,10 @@ public void setSamplingDecision(final @Nullable TracesSamplingDecision samplingD return origin; } + public @NotNull Map getData() { + return data; + } + public void setOrigin(final @Nullable String origin) { this.origin = origin; } @@ -296,6 +303,7 @@ public static final class JsonKeys { public static final String STATUS = "status"; public static final String TAGS = "tags"; public static final String ORIGIN = "origin"; + public static final String DATA = "data"; } @Override @@ -320,6 +328,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (origin != null) { writer.name(JsonKeys.ORIGIN).value(logger, origin); } + writer.name(JsonKeys.DATA).value(logger, data); if (!tags.isEmpty()) { writer.name(JsonKeys.TAGS).value(logger, tags); } @@ -356,6 +365,7 @@ public static final class Deserializer implements JsonDeserializer String description = null; SpanStatus status = null; String origin = null; + Map data = null; Map tags = null; Map unknown = null; @@ -383,6 +393,9 @@ public static final class Deserializer implements JsonDeserializer case JsonKeys.ORIGIN: origin = reader.nextString(); break; + case Request.JsonKeys.DATA: + data = (Map) reader.nextObjectOrNull(); + break; case JsonKeys.TAGS: tags = CollectionUtils.newConcurrentHashMap( @@ -425,6 +438,9 @@ public static final class Deserializer implements JsonDeserializer spanContext.setDescription(description); spanContext.setStatus(status); spanContext.setOrigin(origin); + if (data != null) { + spanContext.data.putAll(data); + } if (tags != null) { spanContext.tags = tags; } diff --git a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index 3bc42e42084..cbe536239dd 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -81,7 +81,7 @@ public SentryTransaction(final @NotNull SentryTracer sentryTracer) { final SpanContext tracerContext = sentryTracer.getSpanContext(); // tags must be placed on the root of the transaction instead of contexts.trace.tags - contexts.setTrace( + final @NotNull SpanContext traceContext = new SpanContext( tracerContext.getTraceId(), tracerContext.getSpanId(), @@ -90,7 +90,9 @@ public SentryTransaction(final @NotNull SentryTracer sentryTracer) { tracerContext.getDescription(), tracerContext.getSamplingDecision(), tracerContext.getStatus(), - tracerContext.getOrigin())); + tracerContext.getOrigin()); + traceContext.getData().putAll(tracerContext.getData()); + contexts.setTrace(traceContext); for (final Map.Entry tag : tracerContext.getTags().entrySet()) { this.setTag(tag.getKey(), tag.getValue()); } diff --git a/sentry/src/main/java/io/sentry/util/thread/IThreadChecker.java b/sentry/src/main/java/io/sentry/util/thread/IThreadChecker.java index 81af056e711..deea360f8c5 100644 --- a/sentry/src/main/java/io/sentry/util/thread/IThreadChecker.java +++ b/sentry/src/main/java/io/sentry/util/thread/IThreadChecker.java @@ -32,6 +32,14 @@ public interface IThreadChecker { */ boolean isMainThread(final @NotNull SentryThread sentryThread); + /** + * Returns the name of the current thread + * + * @return the name of the current thread + */ + @NotNull + String getCurrentThreadName(); + /** * Returns the system id of the current thread. Currently only used for Android. * diff --git a/sentry/src/main/java/io/sentry/util/thread/NoOpThreadChecker.java b/sentry/src/main/java/io/sentry/util/thread/NoOpThreadChecker.java index b1497d17e7d..f80a9967859 100644 --- a/sentry/src/main/java/io/sentry/util/thread/NoOpThreadChecker.java +++ b/sentry/src/main/java/io/sentry/util/thread/NoOpThreadChecker.java @@ -33,6 +33,11 @@ public boolean isMainThread(@NotNull SentryThread sentryThread) { return false; } + @Override + public @NotNull String getCurrentThreadName() { + return ""; + } + @Override public long currentThreadSystemId() { return 0; diff --git a/sentry/src/main/java/io/sentry/util/thread/ThreadChecker.java b/sentry/src/main/java/io/sentry/util/thread/ThreadChecker.java index bfa8aac139e..2f9b6fc1d2d 100644 --- a/sentry/src/main/java/io/sentry/util/thread/ThreadChecker.java +++ b/sentry/src/main/java/io/sentry/util/thread/ThreadChecker.java @@ -44,6 +44,11 @@ public boolean isMainThread(final @NotNull SentryThread sentryThread) { return threadId != null && isMainThread(threadId); } + @Override + public @NotNull String getCurrentThreadName() { + return Thread.currentThread().getName(); + } + @Override public long currentThreadSystemId() { return Thread.currentThread().getId(); diff --git a/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt b/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt index 2e8c8d71fc8..da1e3139e2a 100644 --- a/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt @@ -27,7 +27,10 @@ class CheckInSerializationTest { it.traceId = SentryId("f382e3180c714217a81371f8c644aefe") it.spanId = SpanId("85694b9f567145a6") } - ) + ).apply { + data[SpanDataConvention.THREAD_ID] = 10 + data[SpanDataConvention.THREAD_NAME] = "test" + } duration = 12.3 environment = "env" release = "1.0.1" diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index ba6fea8c144..41a8df70d35 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1057,6 +1057,7 @@ class JsonSerializerTest { trace.status = SpanStatus.OK trace.setTag("myTag", "myValue") trace.sampled = true + trace.data["dataKey"] = "dataValue" val tracer = SentryTracer(trace, fixture.scopes) tracer.setData("dataKey", "dataValue") val span = tracer.startChild("child") @@ -1091,6 +1092,9 @@ class JsonSerializerTest { val jsonTrace = (element["contexts"] as Map<*, *>)["trace"] as Map<*, *> assertNotNull(jsonTrace["trace_id"] as String) assertNotNull(jsonTrace["span_id"] as String) + assertNotNull(jsonTrace["data"] as Map<*, *>) { + assertEquals("dataValue", it["dataKey"]) + } assertEquals("http", jsonTrace["op"] as String) assertEquals("some request", jsonTrace["description"] as String) assertEquals("ok", jsonTrace["status"] as String) @@ -1109,7 +1113,10 @@ class JsonSerializerTest { "trace_id": "b156a475de54423d9c1571df97ec7eb6", "span_id": "0a53026963414893", "op": "http", - "status": "ok" + "status": "ok", + "data": { + "data-key": "data-value" + } }, "custom": { "some-key": "some-value" @@ -1149,6 +1156,7 @@ class JsonSerializerTest { assertEquals("b156a475de54423d9c1571df97ec7eb6", transaction.contexts.trace!!.traceId.toString()) assertEquals("0a53026963414893", transaction.contexts.trace!!.spanId.toString()) assertEquals("http", transaction.contexts.trace!!.operation) + assertEquals("data-value", transaction.contexts.trace!!.data["data-key"]) assertNotNull(transaction.contexts["custom"]) assertEquals("some-value", (transaction.contexts["custom"] as Map<*, *>)["some-key"]) diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index 3c94d9236b1..651115e9538 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -1275,6 +1275,7 @@ class SentryTest { override fun isMainThread(): Boolean = false override fun isMainThread(sentryThread: SentryThread): Boolean = false override fun currentThreadSystemId(): Long = 0 + override fun getCurrentThreadName(): String = "" } private class CustomMemoryCollector : diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 1ede9007f47..e1e1b6413e7 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -1428,6 +1428,7 @@ class SentryTracerTest { fun `when a span is launched on the main thread, the thread info should be set correctly`() { val threadChecker = mock() whenever(threadChecker.isMainThread).thenReturn(true) + whenever(threadChecker.currentThreadName).thenReturn("main") val tracer = fixture.getSut(optionsConfiguration = { options -> options.threadChecker = threadChecker @@ -1441,6 +1442,7 @@ class SentryTracerTest { fun `when a span is launched on the background thread, the thread info should be set correctly`() { val threadChecker = mock() whenever(threadChecker.isMainThread).thenReturn(false) + whenever(threadChecker.currentThreadName).thenReturn("test") val tracer = fixture.getSut(optionsConfiguration = { options -> options.threadChecker = threadChecker diff --git a/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt index bd3bcd72c35..4493b47f706 100644 --- a/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt @@ -6,6 +6,7 @@ import io.sentry.JsonObjectReader import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable import io.sentry.SpanContext +import io.sentry.SpanDataConvention import io.sentry.SpanId import io.sentry.SpanStatus import io.sentry.TracesSamplingDecision @@ -35,6 +36,8 @@ class SpanContextSerializationTest { setTag("2a5fa3f5-7b87-487f-aaa5-84567aa73642", "4781d51a-c5af-47f2-a4ed-f030c9b3e194") setTag("29106d7d-7fa4-444f-9d34-b9d7510c69ab", "218c23ea-694a-497e-bf6d-e5f26f1ad7bd") setTag("ba9ce913-269f-4c03-882d-8ca5e6991b14", "35a74e90-8db8-4610-a411-872cbc1030ac") + data[SpanDataConvention.THREAD_NAME] = "test" + data[SpanDataConvention.THREAD_ID] = 10 } } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/util/thread/ThreadCheckerTest.kt b/sentry/src/test/java/io/sentry/util/thread/ThreadCheckerTest.kt index 26de021fbdc..12b1e348271 100644 --- a/sentry/src/test/java/io/sentry/util/thread/ThreadCheckerTest.kt +++ b/sentry/src/test/java/io/sentry/util/thread/ThreadCheckerTest.kt @@ -2,6 +2,7 @@ package io.sentry.util.thread import io.sentry.protocol.SentryThread import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -43,4 +44,11 @@ class ThreadCheckerTest { } assertFalse(threadChecker.isMainThread(sentryThread)) } + + @Test + fun `currentThreadName returns the name of the current thread`() { + val thread = Thread.currentThread() + thread.name = "test" + assertEquals("test", threadChecker.currentThreadName) + } } diff --git a/sentry/src/test/resources/json/checkin_crontab.json b/sentry/src/test/resources/json/checkin_crontab.json index 8c396858784..c2bff2a050e 100644 --- a/sentry/src/test/resources/json/checkin_crontab.json +++ b/sentry/src/test/resources/json/checkin_crontab.json @@ -25,7 +25,12 @@ "trace_id": "f382e3180c714217a81371f8c644aefe", "span_id": "85694b9f567145a6", "op": "default", - "origin": "manual" + "origin": "manual", + "data": + { + "thread.name": "test", + "thread.id": 10 + } } } } diff --git a/sentry/src/test/resources/json/checkin_interval.json b/sentry/src/test/resources/json/checkin_interval.json index 8281ca67abb..395bb03bbab 100644 --- a/sentry/src/test/resources/json/checkin_interval.json +++ b/sentry/src/test/resources/json/checkin_interval.json @@ -26,7 +26,12 @@ "trace_id": "f382e3180c714217a81371f8c644aefe", "span_id": "85694b9f567145a6", "op": "default", - "origin": "manual" + "origin": "manual", + "data": + { + "thread.name": "test", + "thread.id": 10 + } } } } diff --git a/sentry/src/test/resources/json/contexts.json b/sentry/src/test/resources/json/contexts.json index 574bb019214..56975901cbc 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -116,6 +116,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_base_event.json b/sentry/src/test/resources/json/sentry_base_event.json index afaf0408638..f31c961a33d 100644 --- a/sentry/src/test/resources/json/sentry_base_event.json +++ b/sentry/src/test/resources/json/sentry_base_event.json @@ -119,6 +119,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json index 018cc7aae7d..4534dfa4a33 100644 --- a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json +++ b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json @@ -119,6 +119,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_event.json b/sentry/src/test/resources/json/sentry_event.json index 6d0b351ed49..2a4ba6006f5 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -254,6 +254,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_replay_event.json b/sentry/src/test/resources/json/sentry_replay_event.json index f026c9fee47..4aa14849128 100644 --- a/sentry/src/test/resources/json/sentry_replay_event.json +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -137,6 +137,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index 944a0bfe92d..0153cd07990 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -202,6 +202,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json index 789c4fe2a93..21f6d07fd98 100644 --- a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json +++ b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json @@ -202,6 +202,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json index 330555c1ec0..c30f3a45e9a 100644 --- a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json +++ b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json @@ -172,6 +172,11 @@ "op": "e481581d-35a4-4e97-8a1c-b554bf49f23e", "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", diff --git a/sentry/src/test/resources/json/span_context.json b/sentry/src/test/resources/json/span_context.json index 4a6e08bcc2d..20748f8dff9 100644 --- a/sentry/src/test/resources/json/span_context.json +++ b/sentry/src/test/resources/json/span_context.json @@ -6,6 +6,11 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", + "data": + { + "thread.name": "test", + "thread.id": 10 + }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", From 9233f8a6577b86d69080d012f17632282d5209e3 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 12 Nov 2024 18:16:37 +0100 Subject: [PATCH 08/10] updated tests --- .../core/AndroidContinuousProfilerTest.kt | 4 +- sentry/api/sentry.api | 78 +++++++++++-------- sentry/src/test/resources/json/contexts.json | 9 +-- .../resources/json/sentry_base_event.json | 9 +-- .../sentry_base_event_with_null_extra.json | 9 +-- .../src/test/resources/json/sentry_event.json | 9 +-- .../resources/json/sentry_replay_event.json | 9 +-- .../resources/json/sentry_transaction.json | 9 +-- ...sentry_transaction_legacy_date_format.json | 9 +-- ...entry_transaction_no_measurement_unit.json | 9 +-- .../src/test/resources/json/span_context.json | 9 +-- 11 files changed, 75 insertions(+), 88 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index d3d9266a5ac..6e532f8f2b3 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -353,8 +353,8 @@ class AndroidContinuousProfilerTest { val performanceCollector = mock() val collectionData = PerformanceCollectionData() - collectionData.addMemoryData(MemoryCollectionData(1, 2, 3, SentryNanotimeDate())) - collectionData.addCpuData(CpuCollectionData(1, 3.0, SentryNanotimeDate())) + collectionData.addMemoryData(MemoryCollectionData(2, 3, SentryNanotimeDate())) + collectionData.addCpuData(CpuCollectionData(3.0, SentryNanotimeDate())) whenever(performanceCollector.stop(any())).thenReturn(listOf(collectionData)) fixture.options.compositePerformanceCollector = performanceCollector diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index babc5d1db41..5709c80f667 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -311,6 +311,16 @@ public final class io/sentry/CombinedScopeView : io/sentry/IScope { public fun withTransaction (Lio/sentry/Scope$IWithTransaction;)V } +public abstract interface class io/sentry/CompositePerformanceCollector { + public abstract fun close ()V + public abstract fun onSpanFinished (Lio/sentry/ISpan;)V + public abstract fun onSpanStarted (Lio/sentry/ISpan;)V + public abstract fun start (Lio/sentry/ITransaction;)V + public abstract fun start (Ljava/lang/String;)V + public abstract fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public abstract fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/CpuCollectionData { public fun (DLio/sentry/SentryDate;)V public fun getCpuUsagePercentage ()D @@ -367,6 +377,17 @@ public final class io/sentry/DeduplicateMultithreadedEventProcessor : io/sentry/ public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; } +public final class io/sentry/DefaultCompositePerformanceCollector : io/sentry/CompositePerformanceCollector { + public fun (Lio/sentry/SentryOptions;)V + public fun close ()V + public fun onSpanFinished (Lio/sentry/ISpan;)V + public fun onSpanStarted (Lio/sentry/ISpan;)V + public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V + public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/DefaultScopesStorage : io/sentry/IScopesStorage { public fun ()V public fun close ()V @@ -377,16 +398,7 @@ public final class io/sentry/DefaultScopesStorage : io/sentry/IScopesStorage { public final class io/sentry/DefaultSpanFactory : io/sentry/ISpanFactory { public fun ()V public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; -} - -public final class io/sentry/DefaultTransactionPerformanceCollector : io/sentry/TransactionPerformanceCollector { - public fun (Lio/sentry/SentryOptions;)V - public fun close ()V - public fun onSpanFinished (Lio/sentry/ISpan;)V - public fun onSpanStarted (Lio/sentry/ISpan;)V - public fun start (Lio/sentry/ITransaction;)V - public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public final class io/sentry/DiagnosticLogger : io/sentry/ILogger { @@ -700,6 +712,7 @@ public abstract interface class io/sentry/IConnectionStatusProvider$IConnectionS public abstract interface class io/sentry/IContinuousProfiler { public abstract fun close ()V + public abstract fun getProfilerId ()Lio/sentry/protocol/SentryId; public abstract fun isRunning ()Z public abstract fun setScopes (Lio/sentry/IScopes;)V public abstract fun start ()V @@ -1018,7 +1031,7 @@ public abstract interface class io/sentry/ISpan { public abstract interface class io/sentry/ISpanFactory { public abstract fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public abstract fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public abstract fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public abstract interface class io/sentry/ITransaction : io/sentry/ISpan { @@ -1358,6 +1371,17 @@ public final class io/sentry/MonitorScheduleUnit : java/lang/Enum { public static fun values ()[Lio/sentry/MonitorScheduleUnit; } +public final class io/sentry/NoOpCompositePerformanceCollector : io/sentry/CompositePerformanceCollector { + public fun close ()V + public static fun getInstance ()Lio/sentry/NoOpCompositePerformanceCollector; + public fun onSpanFinished (Lio/sentry/ISpan;)V + public fun onSpanStarted (Lio/sentry/ISpan;)V + public fun start (Lio/sentry/ITransaction;)V + public fun start (Ljava/lang/String;)V + public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; + public fun stop (Ljava/lang/String;)Ljava/util/List; +} + public final class io/sentry/NoOpConnectionStatusProvider : io/sentry/IConnectionStatusProvider { public fun ()V public fun addConnectionStatusObserver (Lio/sentry/IConnectionStatusProvider$IConnectionStatusObserver;)Z @@ -1369,6 +1393,7 @@ public final class io/sentry/NoOpConnectionStatusProvider : io/sentry/IConnectio public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfiler { public fun close ()V public static fun getInstance ()Lio/sentry/NoOpContinuousProfiler; + public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z public fun setScopes (Lio/sentry/IScopes;)V public fun start ()V @@ -1661,7 +1686,7 @@ public final class io/sentry/NoOpSpan : io/sentry/ISpan { public final class io/sentry/NoOpSpanFactory : io/sentry/ISpanFactory { public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; public static fun getInstance ()Lio/sentry/NoOpSpanFactory; } @@ -1718,15 +1743,6 @@ public final class io/sentry/NoOpTransaction : io/sentry/ITransaction { public fun updateEndDate (Lio/sentry/SentryDate;)Z } -public final class io/sentry/NoOpTransactionPerformanceCollector : io/sentry/TransactionPerformanceCollector { - public fun close ()V - public static fun getInstance ()Lio/sentry/NoOpTransactionPerformanceCollector; - public fun onSpanFinished (Lio/sentry/ISpan;)V - public fun onSpanStarted (Lio/sentry/ISpan;)V - public fun start (Lio/sentry/ITransaction;)V - public fun stop (Lio/sentry/ITransaction;)Ljava/util/List; -} - public final class io/sentry/NoOpTransactionProfiler : io/sentry/ITransactionProfiler { public fun bindTransaction (Lio/sentry/ITransaction;)V public fun close ()V @@ -1860,7 +1876,7 @@ public final class io/sentry/ProfileChunk$JsonKeys { public fun ()V } -public class io/sentry/ProfileContext : io/sentry/JsonSerializable, io/sentry/JsonUnknown { +public final class io/sentry/ProfileContext : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public static final field TYPE Ljava/lang/String; public fun ()V public fun (Lio/sentry/ProfileContext;)V @@ -2899,9 +2915,11 @@ public class io/sentry/SentryOptions { public fun getBundleIds ()Ljava/util/Set; public fun getCacheDirPath ()Ljava/lang/String; public fun getClientReportRecorder ()Lio/sentry/clientreport/IClientReportRecorder; + public fun getCompositePerformanceCollector ()Lio/sentry/CompositePerformanceCollector; public fun getConnectionStatusProvider ()Lio/sentry/IConnectionStatusProvider; public fun getConnectionTimeoutMillis ()I public fun getContextTags ()Ljava/util/List; + public fun getContinuousProfiler ()Lio/sentry/IContinuousProfiler; public fun getCron ()Lio/sentry/SentryOptions$Cron; public fun getDateProvider ()Lio/sentry/SentryDateProvider; public fun getDebugMetaLoader ()Lio/sentry/internal/debugmeta/IDebugMetaLoader; @@ -2968,7 +2986,6 @@ public class io/sentry/SentryOptions { public fun getTracePropagationTargets ()Ljava/util/List; public fun getTracesSampleRate ()Ljava/lang/Double; public fun getTracesSampler ()Lio/sentry/SentryOptions$TracesSamplerCallback; - public fun getTransactionPerformanceCollector ()Lio/sentry/TransactionPerformanceCollector; public fun getTransactionProfiler ()Lio/sentry/ITransactionProfiler; public fun getTransportFactory ()Lio/sentry/ITransportFactory; public fun getTransportGate ()Lio/sentry/transport/ITransportGate; @@ -3012,8 +3029,10 @@ public class io/sentry/SentryOptions { public fun setBeforeSend (Lio/sentry/SentryOptions$BeforeSendCallback;)V public fun setBeforeSendTransaction (Lio/sentry/SentryOptions$BeforeSendTransactionCallback;)V public fun setCacheDirPath (Ljava/lang/String;)V + public fun setCompositePerformanceCollector (Lio/sentry/CompositePerformanceCollector;)V public fun setConnectionStatusProvider (Lio/sentry/IConnectionStatusProvider;)V public fun setConnectionTimeoutMillis (I)V + public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V public fun setCron (Lio/sentry/SentryOptions$Cron;)V public fun setDateProvider (Lio/sentry/SentryDateProvider;)V public fun setDebug (Z)V @@ -3092,7 +3111,6 @@ public class io/sentry/SentryOptions { public fun setTraceSampling (Z)V public fun setTracesSampleRate (Ljava/lang/Double;)V public fun setTracesSampler (Lio/sentry/SentryOptions$TracesSamplerCallback;)V - public fun setTransactionPerformanceCollector (Lio/sentry/TransactionPerformanceCollector;)V public fun setTransactionProfiler (Lio/sentry/ITransactionProfiler;)V public fun setTransportFactory (Lio/sentry/ITransportFactory;)V public fun setTransportGate (Lio/sentry/transport/ITransportGate;)V @@ -3579,6 +3597,7 @@ public abstract interface class io/sentry/SpanDataConvention { public static final field HTTP_RESPONSE_CONTENT_LENGTH_KEY Ljava/lang/String; public static final field HTTP_START_TIMESTAMP Ljava/lang/String; public static final field HTTP_STATUS_CODE_KEY Ljava/lang/String; + public static final field PROFILER_ID Ljava/lang/String; public static final field THREAD_ID Ljava/lang/String; public static final field THREAD_NAME Ljava/lang/String; } @@ -3762,14 +3781,6 @@ public final class io/sentry/TransactionOptions : io/sentry/SpanOptions { public fun setWaitForChildren (Z)V } -public abstract interface class io/sentry/TransactionPerformanceCollector { - public abstract fun close ()V - public abstract fun onSpanFinished (Lio/sentry/ISpan;)V - public abstract fun onSpanStarted (Lio/sentry/ISpan;)V - public abstract fun start (Lio/sentry/ITransaction;)V - public abstract fun stop (Lio/sentry/ITransaction;)Ljava/util/List; -} - public final class io/sentry/TypeCheckHint { public static final field ANDROID_ACTIVITY Ljava/lang/String; public static final field ANDROID_CONFIGURATION Ljava/lang/String; @@ -6341,6 +6352,7 @@ public final class io/sentry/util/UrlUtils$UrlDetails { public abstract interface class io/sentry/util/thread/IThreadChecker { public abstract fun currentThreadSystemId ()J + public abstract fun getCurrentThreadName ()Ljava/lang/String; public abstract fun isMainThread ()Z public abstract fun isMainThread (J)Z public abstract fun isMainThread (Lio/sentry/protocol/SentryThread;)Z @@ -6350,6 +6362,7 @@ public abstract interface class io/sentry/util/thread/IThreadChecker { public final class io/sentry/util/thread/NoOpThreadChecker : io/sentry/util/thread/IThreadChecker { public fun ()V public fun currentThreadSystemId ()J + public fun getCurrentThreadName ()Ljava/lang/String; public static fun getInstance ()Lio/sentry/util/thread/NoOpThreadChecker; public fun isMainThread ()Z public fun isMainThread (J)Z @@ -6359,6 +6372,7 @@ public final class io/sentry/util/thread/NoOpThreadChecker : io/sentry/util/thre public final class io/sentry/util/thread/ThreadChecker : io/sentry/util/thread/IThreadChecker { public fun currentThreadSystemId ()J + public fun getCurrentThreadName ()Ljava/lang/String; public static fun getInstance ()Lio/sentry/util/thread/ThreadChecker; public fun isMainThread ()Z public fun isMainThread (J)Z diff --git a/sentry/src/test/resources/json/contexts.json b/sentry/src/test/resources/json/contexts.json index e4c14443c86..e8df6a21c10 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -115,11 +115,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -128,7 +123,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } } diff --git a/sentry/src/test/resources/json/sentry_base_event.json b/sentry/src/test/resources/json/sentry_base_event.json index 1dd79d6f7cb..63ae8f03cf3 100644 --- a/sentry/src/test/resources/json/sentry_base_event.json +++ b/sentry/src/test/resources/json/sentry_base_event.json @@ -118,11 +118,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -131,7 +126,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json index 652d2adb4fd..2079b424cb6 100644 --- a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json +++ b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json @@ -118,11 +118,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -131,7 +126,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_event.json b/sentry/src/test/resources/json/sentry_event.json index 56f52e2c5da..c6f8dd68b02 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -253,11 +253,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -266,7 +261,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_replay_event.json b/sentry/src/test/resources/json/sentry_replay_event.json index 52089f3a89d..7bd64037d77 100644 --- a/sentry/src/test/resources/json/sentry_replay_event.json +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -136,11 +136,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -149,7 +144,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index d0924d2244b..daa6d025e9a 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -175,11 +175,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -188,7 +183,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json index ad43ac3629e..316b44bbaaa 100644 --- a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json +++ b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json @@ -175,11 +175,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -188,7 +183,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json index 4c60886c8fa..cf927b322b6 100644 --- a/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json +++ b/sentry/src/test/resources/json/sentry_transaction_no_measurement_unit.json @@ -145,11 +145,6 @@ "op": "e481581d-35a4-4e97-8a1c-b554bf49f23e", "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -158,7 +153,9 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } }, diff --git a/sentry/src/test/resources/json/span_context.json b/sentry/src/test/resources/json/span_context.json index 348712e7909..edff574fa4d 100644 --- a/sentry/src/test/resources/json/span_context.json +++ b/sentry/src/test/resources/json/span_context.json @@ -6,11 +6,6 @@ "description": "c204b6c7-9753-4d45-927d-b19789bfc9a5", "status": "resource_exhausted", "origin": "auto.test.unit.spancontext", - "data": - { - "thread.name": "test", - "thread.id": 10 - }, "tags": { "2a5fa3f5-7b87-487f-aaa5-84567aa73642": "4781d51a-c5af-47f2-a4ed-f030c9b3e194", @@ -19,6 +14,8 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } } From c0a7fd12eb32e615ea738dc35d649bfd2f37dcd5 Mon Sep 17 00:00:00 2001 From: Stefano Date: Thu, 14 Nov 2024 15:49:24 +0100 Subject: [PATCH 09/10] Handle App Start Continuous Profiling v8 (p4) (#3730) * create app start continuous profiler instead of transaction profiler, based on config * updated SentryAppStartProfilingOptions with isContinuousProfilingEnabled flag * updated SentryOptions with isContinuousProfilingEnabled() method * cut profiler setup out in a specific function to improve readability of AndroidOptionsInitializer Add new APIs for Continuous Profiling v8 (p5) (#3844) * AndroidContinuousProfiler now retrieve the scopes on start() * removed profilesSampleRate from sample app to enable continuous profiling * added Sentry.startProfiler and Sentry.stopProfiler APIs --- .../api/sentry-android-core.api | 3 +- .../core/AndroidContinuousProfiler.java | 14 ++- .../core/AndroidOptionsInitializer.java | 104 ++++++++++++------ .../core/SentryPerformanceProvider.java | 93 +++++++++++----- .../core/performance/AppStartMetrics.java | 21 +++- .../core/AndroidContinuousProfilerTest.kt | 5 +- .../core/AndroidOptionsInitializerTest.kt | 83 ++++++++++++++ .../core/SentryPerformanceProviderTest.kt | 65 ++++++++++- .../core/performance/AppStartMetricsTest.kt | 32 +++++- .../src/main/AndroidManifest.xml | 2 +- .../sentry/samples/android/MyApplication.java | 2 + sentry/api/sentry.api | 22 +++- .../src/main/java/io/sentry/HubAdapter.java | 10 ++ .../main/java/io/sentry/HubScopesWrapper.java | 10 ++ .../java/io/sentry/IContinuousProfiler.java | 2 - sentry/src/main/java/io/sentry/IScopes.java | 4 + .../io/sentry/NoOpContinuousProfiler.java | 3 - sentry/src/main/java/io/sentry/NoOpHub.java | 6 + .../src/main/java/io/sentry/NoOpScopes.java | 6 + sentry/src/main/java/io/sentry/Scopes.java | 28 +++++ .../main/java/io/sentry/ScopesAdapter.java | 10 ++ sentry/src/main/java/io/sentry/Sentry.java | 10 ++ .../SentryAppStartProfilingOptions.java | 21 ++++ .../main/java/io/sentry/SentryOptions.java | 11 ++ .../src/test/java/io/sentry/HubAdapterTest.kt | 10 ++ .../test/java/io/sentry/JsonSerializerTest.kt | 5 +- sentry/src/test/java/io/sentry/NoOpHubTest.kt | 6 + .../test/java/io/sentry/ScopesAdapterTest.kt | 10 ++ sentry/src/test/java/io/sentry/ScopesTest.kt | 50 +++++++++ .../test/java/io/sentry/SentryOptionsTest.kt | 15 ++- sentry/src/test/java/io/sentry/SentryTest.kt | 46 ++++++++ 31 files changed, 618 insertions(+), 91 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 5b3dd6ed833..dd4cfdf3530 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -41,7 +41,6 @@ public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IConti public fun close ()V public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z - public fun setScopes (Lio/sentry/IScopes;)V public fun start ()V public fun stop ()V } @@ -448,6 +447,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V public fun clear ()V public fun getActivityLifecycleTimeSpans ()Ljava/util/List; + public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler; public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler; public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision; public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan; @@ -466,6 +466,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V public fun registerApplicationForegroundCheck (Landroid/app/Application;)V public fun setAppLaunchedInForeground (Z)V + public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 12d21293837..0536dcabc72 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -9,8 +9,10 @@ import io.sentry.ILogger; import io.sentry.IScopes; import io.sentry.ISentryExecutorService; +import io.sentry.NoOpScopes; import io.sentry.PerformanceCollectionData; import io.sentry.ProfileChunk; +import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; @@ -88,12 +90,14 @@ private void init() { logger); } - public synchronized void setScopes(final @NotNull IScopes scopes) { - this.scopes = scopes; - this.performanceCollector = scopes.getOptions().getCompositePerformanceCollector(); - } - public synchronized void start() { + if ((scopes == null || scopes != NoOpScopes.getInstance()) + && Sentry.getCurrentScopes() != NoOpScopes.getInstance()) { + this.scopes = Sentry.getCurrentScopes(); + this.performanceCollector = + Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector(); + } + // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 788c63aa4bc..605294f6667 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -7,6 +7,7 @@ import android.content.pm.PackageInfo; import io.sentry.DeduplicateMultithreadedEventProcessor; import io.sentry.DefaultCompositePerformanceCollector; +import io.sentry.IContinuousProfiler; import io.sentry.ILogger; import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; @@ -158,43 +159,26 @@ static void initializeIntegrationsAndProcessors( options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider)); options.setTransportGate(new AndroidTransportGate(options)); - if (options.isProfilingEnabled()) { - options.setContinuousProfiler(NoOpContinuousProfiler.getInstance()); - // Check if the profiler was already instantiated in the app start. - // We use the Android profiler, that uses a global start/stop api, so we need to preserve the - // state of the profiler, and it's only possible retaining the instance. - try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { - final @Nullable ITransactionProfiler appStartProfiler = - AppStartMetrics.getInstance().getAppStartProfiler(); - if (appStartProfiler != null) { - options.setTransactionProfiler(appStartProfiler); - AppStartMetrics.getInstance().setAppStartProfiler(null); - } else { - options.setTransactionProfiler( - new AndroidTransactionProfiler( - context, - options, - buildInfoProvider, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"))); - } - } - } else { - options.setTransactionProfiler(NoOpTransactionProfiler.getInstance()); - // todo handle app start continuous profiler - options.setContinuousProfiler( - new AndroidContinuousProfiler( - buildInfoProvider, - Objects.requireNonNull( - options.getFrameMetricsCollector(), - "options.getFrameMetricsCollector is required"), - options.getLogger(), - options.getProfilingTracesDirPath(), - options.getProfilingTracesHz(), - options.getExecutorService())); + // Check if the profiler was already instantiated in the app start. + // We use the Android profiler, that uses a global start/stop api, so we need to preserve the + // state of the profiler, and it's only possible retaining the instance. + final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); + final @Nullable ITransactionProfiler appStartTransactionProfiler; + final @Nullable IContinuousProfiler appStartContinuousProfiler; + try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { + appStartTransactionProfiler = appStartMetrics.getAppStartProfiler(); + appStartContinuousProfiler = appStartMetrics.getAppStartContinuousProfiler(); + appStartMetrics.setAppStartProfiler(null); + appStartMetrics.setAppStartContinuousProfiler(null); } + setupProfiler( + options, + context, + buildInfoProvider, + appStartTransactionProfiler, + appStartContinuousProfiler); + options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger())); options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger())); @@ -252,6 +236,56 @@ static void initializeIntegrationsAndProcessors( } } + /** Setup the correct profiler (transaction or continuous) based on the options. */ + private static void setupProfiler( + final @NotNull SentryAndroidOptions options, + final @NotNull Context context, + final @NotNull BuildInfoProvider buildInfoProvider, + final @Nullable ITransactionProfiler appStartTransactionProfiler, + final @Nullable IContinuousProfiler appStartContinuousProfiler) { + if (options.isProfilingEnabled() || options.getProfilesSampleRate() != null) { + options.setContinuousProfiler(NoOpContinuousProfiler.getInstance()); + // This is a safeguard, but it should never happen, as the app start profiler should be the + // continuous one. + if (appStartContinuousProfiler != null) { + appStartContinuousProfiler.close(); + } + if (appStartTransactionProfiler != null) { + options.setTransactionProfiler(appStartTransactionProfiler); + } else { + options.setTransactionProfiler( + new AndroidTransactionProfiler( + context, + options, + buildInfoProvider, + Objects.requireNonNull( + options.getFrameMetricsCollector(), + "options.getFrameMetricsCollector is required"))); + } + } else { + options.setTransactionProfiler(NoOpTransactionProfiler.getInstance()); + // This is a safeguard, but it should never happen, as the app start profiler should be the + // transaction one. + if (appStartTransactionProfiler != null) { + appStartTransactionProfiler.close(); + } + if (appStartContinuousProfiler != null) { + options.setContinuousProfiler(appStartContinuousProfiler); + } else { + options.setContinuousProfiler( + new AndroidContinuousProfiler( + buildInfoProvider, + Objects.requireNonNull( + options.getFrameMetricsCollector(), + "options.getFrameMetricsCollector is required"), + options.getLogger(), + options.getProfilingTracesDirPath(), + options.getProfilingTracesHz(), + options.getExecutorService())); + } + } + } + static void installDefaultIntegrations( final @NotNull Context context, final @NotNull SentryAndroidOptions options, diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index 64a1ceda60e..1d76775b3f8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -12,6 +12,7 @@ import android.os.Process; import android.os.SystemClock; import androidx.annotation.NonNull; +import io.sentry.IContinuousProfiler; import io.sentry.ILogger; import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; @@ -100,6 +101,11 @@ public void shutdown() { if (appStartProfiler != null) { appStartProfiler.close(); } + final @Nullable IContinuousProfiler appStartContinuousProfiler = + AppStartMetrics.getInstance().getAppStartContinuousProfiler(); + if (appStartContinuousProfiler != null) { + appStartContinuousProfiler.close(); + } } } @@ -132,40 +138,18 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri return; } + if (profilingOptions.isContinuousProfilingEnabled()) { + createAndStartContinuousProfiler(context, profilingOptions, appStartMetrics); + return; + } + if (!profilingOptions.isProfilingEnabled()) { logger.log( SentryLevel.INFO, "Profiling is not enabled. App start profiling will not start."); return; } - final @NotNull TracesSamplingDecision appStartSamplingDecision = - new TracesSamplingDecision( - profilingOptions.isTraceSampled(), - profilingOptions.getTraceSampleRate(), - profilingOptions.isProfileSampled(), - profilingOptions.getProfileSampleRate()); - // We store any sampling decision, so we can respect it when the first transaction starts - appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision); - - if (!(appStartSamplingDecision.getProfileSampled() - && appStartSamplingDecision.getSampled())) { - logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start."); - return; - } - logger.log(SentryLevel.DEBUG, "App start profiling started."); - - final @NotNull ITransactionProfiler appStartProfiler = - new AndroidTransactionProfiler( - context, - buildInfoProvider, - new SentryFrameMetricsCollector(context, logger, buildInfoProvider), - logger, - profilingOptions.getProfilingTracesDirPath(), - profilingOptions.isProfilingEnabled(), - profilingOptions.getProfilingTracesHz(), - new SentryExecutorService()); - appStartMetrics.setAppStartProfiler(appStartProfiler); - appStartProfiler.start(); + createAndStartTransactionProfiler(context, profilingOptions, appStartMetrics); } catch (FileNotFoundException e) { logger.log(SentryLevel.ERROR, "App start profiling config file not found. ", e); @@ -174,6 +158,59 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri } } + private void createAndStartContinuousProfiler( + final @NotNull Context context, + final @NotNull SentryAppStartProfilingOptions profilingOptions, + final @NotNull AppStartMetrics appStartMetrics) { + final @NotNull IContinuousProfiler appStartContinuousProfiler = + new AndroidContinuousProfiler( + buildInfoProvider, + new SentryFrameMetricsCollector( + context.getApplicationContext(), logger, buildInfoProvider), + logger, + profilingOptions.getProfilingTracesDirPath(), + profilingOptions.getProfilingTracesHz(), + new SentryExecutorService()); + appStartMetrics.setAppStartProfiler(null); + appStartMetrics.setAppStartContinuousProfiler(appStartContinuousProfiler); + logger.log(SentryLevel.DEBUG, "App start continuous profiling started."); + appStartContinuousProfiler.start(); + } + + private void createAndStartTransactionProfiler( + final @NotNull Context context, + final @NotNull SentryAppStartProfilingOptions profilingOptions, + final @NotNull AppStartMetrics appStartMetrics) { + final @NotNull TracesSamplingDecision appStartSamplingDecision = + new TracesSamplingDecision( + profilingOptions.isTraceSampled(), + profilingOptions.getTraceSampleRate(), + profilingOptions.isProfileSampled(), + profilingOptions.getProfileSampleRate()); + // We store any sampling decision, so we can respect it when the first transaction starts + appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision); + + if (!(appStartSamplingDecision.getProfileSampled() && appStartSamplingDecision.getSampled())) { + logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start."); + return; + } + + final @NotNull ITransactionProfiler appStartProfiler = + new AndroidTransactionProfiler( + context, + buildInfoProvider, + new SentryFrameMetricsCollector(context, logger, buildInfoProvider), + logger, + profilingOptions.getProfilingTracesDirPath(), + profilingOptions.isProfilingEnabled(), + profilingOptions.getProfilingTracesHz(), + new SentryExecutorService()); + appStartMetrics.setAppStartContinuousProfiler(null); + appStartMetrics.setAppStartProfiler(appStartProfiler); + logger.log(SentryLevel.DEBUG, "App start profiling started."); + appStartProfiler.start(); + } + @SuppressLint("NewApi") private void onAppLaunched( final @Nullable Context context, final @NotNull AppStartMetrics appStartMetrics) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 996c6ab171b..f43b44a5ad1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import io.sentry.IContinuousProfiler; import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; import io.sentry.SentryDate; @@ -57,6 +58,7 @@ public enum AppStartType { private final @NotNull Map contentProviderOnCreates; private final @NotNull List activityLifecycles; private @Nullable ITransactionProfiler appStartProfiler = null; + private @Nullable IContinuousProfiler appStartContinuousProfiler = null; private @Nullable TracesSamplingDecision appStartSamplingDecision = null; private @Nullable SentryDate onCreateTime = null; private boolean appLaunchTooLong = false; @@ -186,6 +188,10 @@ public void clear() { appStartProfiler.close(); } appStartProfiler = null; + if (appStartContinuousProfiler != null) { + appStartContinuousProfiler.close(); + } + appStartContinuousProfiler = null; appStartSamplingDecision = null; appLaunchTooLong = false; appLaunchedInForeground = false; @@ -201,6 +207,15 @@ public void setAppStartProfiler(final @Nullable ITransactionProfiler appStartPro this.appStartProfiler = appStartProfiler; } + public @Nullable IContinuousProfiler getAppStartContinuousProfiler() { + return appStartContinuousProfiler; + } + + public void setAppStartContinuousProfiler( + final @Nullable IContinuousProfiler appStartContinuousProfiler) { + this.appStartContinuousProfiler = appStartContinuousProfiler; + } + public void setAppStartSamplingDecision( final @Nullable TracesSamplingDecision appStartSamplingDecision) { this.appStartSamplingDecision = appStartSamplingDecision; @@ -259,11 +274,15 @@ private void checkCreateTimeOnMain(final @NotNull Application application) { if (onCreateTime == null) { appLaunchedInForeground = false; - // we stop the app start profiler, as it's useless and likely to timeout + // we stop the app start profilers, as they are useless and likely to timeout if (appStartProfiler != null && appStartProfiler.isRunning()) { appStartProfiler.close(); appStartProfiler = null; } + if (appStartContinuousProfiler != null && appStartContinuousProfiler.isRunning()) { + appStartContinuousProfiler.close(); + appStartContinuousProfiler = null; + } } application.unregisterActivityLifecycleCallbacks(instance); }); diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index 6e532f8f2b3..70ed75186c9 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -11,6 +11,7 @@ import io.sentry.IScopes import io.sentry.ISentryExecutorService import io.sentry.MemoryCollectionData import io.sentry.PerformanceCollectionData +import io.sentry.Sentry import io.sentry.SentryLevel import io.sentry.SentryNanotimeDate import io.sentry.SentryTracer @@ -80,7 +81,7 @@ class AndroidContinuousProfilerTest { options.profilingTracesDirPath, options.profilingTracesHz, options.executorService - ).also { it.setScopes(scopes) } + ) } } @@ -118,6 +119,8 @@ class AndroidContinuousProfilerTest { // Profiler doesn't start if the folder doesn't exists. // Usually it's generated when calling Sentry.init, but for tests we can create it manually. File(fixture.options.profilingTracesDirPath!!).mkdirs() + + Sentry.setCurrentScopes(fixture.scopes) } @AfterTest diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt index 2e00b24e63d..56571b4431d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt @@ -7,7 +7,9 @@ import android.os.Bundle import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.DefaultCompositePerformanceCollector +import io.sentry.IContinuousProfiler import io.sentry.ILogger +import io.sentry.ITransactionProfiler import io.sentry.MainEventProcessor import io.sentry.NoOpContinuousProfiler import io.sentry.NoOpTransactionProfiler @@ -16,6 +18,7 @@ import io.sentry.android.core.cache.AndroidEnvelopeCache import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator import io.sentry.android.core.internal.modules.AssetsModulesLoader import io.sentry.android.core.internal.util.AndroidThreadChecker +import io.sentry.android.core.performance.AppStartMetrics import io.sentry.android.fragment.FragmentLifecycleIntegration import io.sentry.android.replay.ReplayIntegration import io.sentry.android.timber.SentryTimberIntegration @@ -37,6 +40,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertIs +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -365,6 +369,17 @@ class AndroidOptionsInitializerTest { assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) } + @Test + fun `init with profilesSampleRate 0 should set Android transaction profiler`() { + fixture.initSut(configureOptions = { + profilesSampleRate = 0.0 + }) + + assertNotNull(fixture.sentryOptions.transactionProfiler) + assertTrue(fixture.sentryOptions.transactionProfiler is AndroidTransactionProfiler) + assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) + } + @Test fun `init with profilesSampler should set Android transaction profiler`() { fixture.initSut(configureOptions = { @@ -376,6 +391,74 @@ class AndroidOptionsInitializerTest { assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) } + @Test + fun `init reuses transaction profiler of appStartMetrics, if exists`() { + val appStartProfiler = mock() + AppStartMetrics.getInstance().appStartProfiler = appStartProfiler + fixture.initSut(configureOptions = { + profilesSampler = mock() + }) + + assertEquals(appStartProfiler, fixture.sentryOptions.transactionProfiler) + assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) + + // AppStartMetrics should be cleared + assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + } + + @Test + fun `init reuses continuous profiler of appStartMetrics, if exists`() { + val appStartContinuousProfiler = mock() + AppStartMetrics.getInstance().appStartContinuousProfiler = appStartContinuousProfiler + fixture.initSut() + + assertEquals(fixture.sentryOptions.transactionProfiler, NoOpTransactionProfiler.getInstance()) + assertEquals(appStartContinuousProfiler, fixture.sentryOptions.continuousProfiler) + + // AppStartMetrics should be cleared + assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + } + + @Test + fun `init with transaction profiling closes continuous profiler of appStartMetrics`() { + val appStartContinuousProfiler = mock() + AppStartMetrics.getInstance().appStartContinuousProfiler = appStartContinuousProfiler + fixture.initSut(configureOptions = { + profilesSampler = mock() + }) + + assertNotNull(fixture.sentryOptions.transactionProfiler) + assertNotEquals(NoOpTransactionProfiler.getInstance(), fixture.sentryOptions.transactionProfiler) + assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance()) + + // app start profiler is closed, because it will never be used + verify(appStartContinuousProfiler).close() + + // AppStartMetrics should be cleared + assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + } + + @Test + fun `init with continuous profiling closes transaction profiler of appStartMetrics`() { + val appStartProfiler = mock() + AppStartMetrics.getInstance().appStartProfiler = appStartProfiler + fixture.initSut() + + assertEquals(NoOpTransactionProfiler.getInstance(), fixture.sentryOptions.transactionProfiler) + assertNotNull(fixture.sentryOptions.continuousProfiler) + assertNotEquals(NoOpContinuousProfiler.getInstance(), fixture.sentryOptions.continuousProfiler) + + // app start profiler is closed, because it will never be used + verify(appStartProfiler).close() + + // AppStartMetrics should be cleared + assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + } + @Test fun `NdkIntegration will load SentryNdk class and add to the integration list`() { fixture.initSutWithClassLoader(classesToLoad = listOfNotNull(NdkIntegration.SENTRY_NDK_CLASS_NAME)) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt index 26a76af30eb..237bc548675 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt @@ -176,6 +176,7 @@ class SentryPerformanceProviderTest { fun `when config file does not exists, nothing happens`() { fixture.getSut() assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) verify(fixture.logger, never()).log(any(), any()) } @@ -186,6 +187,7 @@ class SentryPerformanceProviderTest { config.setReadable(false) } assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) verify(fixture.logger, never()).log(any(), any()) } @@ -195,6 +197,7 @@ class SentryPerformanceProviderTest { config.createNewFile() } assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) verify(fixture.logger).log( eq(SentryLevel.WARNING), eq("Unable to deserialize the SentryAppStartProfilingOptions. App start profiling will not start.") @@ -204,7 +207,7 @@ class SentryPerformanceProviderTest { @Test fun `when profiling is disabled, profiler is not started`() { fixture.getSut { config -> - writeConfig(config, profilingEnabled = false) + writeConfig(config, profilingEnabled = false, continuousProfilingEnabled = false) } assertNull(AppStartMetrics.getInstance().appStartProfiler) verify(fixture.logger).log( @@ -213,10 +216,22 @@ class SentryPerformanceProviderTest { ) } + @Test + fun `when continuous profiling is disabled, continuous profiler is not started`() { + fixture.getSut { config -> + writeConfig(config, continuousProfilingEnabled = false, profilingEnabled = false) + } + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + verify(fixture.logger).log( + eq(SentryLevel.INFO), + eq("Profiling is not enabled. App start profiling will not start.") + ) + } + @Test fun `when trace is not sampled, profiler is not started and sample decision is stored`() { fixture.getSut { config -> - writeConfig(config, traceSampled = false, profileSampled = true) + writeConfig(config, continuousProfilingEnabled = false, traceSampled = false, profileSampled = true) } assertNull(AppStartMetrics.getInstance().appStartProfiler) assertNotNull(AppStartMetrics.getInstance().appStartSamplingDecision) @@ -232,7 +247,7 @@ class SentryPerformanceProviderTest { @Test fun `when profile is not sampled, profiler is not started and sample decision is stored`() { fixture.getSut { config -> - writeConfig(config, traceSampled = true, profileSampled = false) + writeConfig(config, continuousProfilingEnabled = false, traceSampled = true, profileSampled = false) } assertNull(AppStartMetrics.getInstance().appStartProfiler) assertNotNull(AppStartMetrics.getInstance().appStartSamplingDecision) @@ -244,11 +259,26 @@ class SentryPerformanceProviderTest { ) } + // This case should never happen in reality, but it's technically possible to have such configuration @Test - fun `when profiler starts, it is set in AppStartMetrics`() { + fun `when both transaction and continuous profilers are enabled, only continuous profiler is created`() { fixture.getSut { config -> writeConfig(config) } + assertNull(AppStartMetrics.getInstance().appStartProfiler) + assertNotNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + assertTrue(AppStartMetrics.getInstance().appStartContinuousProfiler!!.isRunning) + verify(fixture.logger).log( + eq(SentryLevel.DEBUG), + eq("App start continuous profiling started.") + ) + } + + @Test + fun `when profiler starts, it is set in AppStartMetrics`() { + fixture.getSut { config -> + writeConfig(config, continuousProfilingEnabled = false) + } assertNotNull(AppStartMetrics.getInstance().appStartProfiler) assertNotNull(AppStartMetrics.getInstance().appStartSamplingDecision) assertTrue(AppStartMetrics.getInstance().appStartProfiler!!.isRunning) @@ -260,19 +290,43 @@ class SentryPerformanceProviderTest { ) } + @Test + fun `when continuous profiler starts, it is set in AppStartMetrics`() { + fixture.getSut { config -> + writeConfig(config, profilingEnabled = false) + } + assertNotNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + assertTrue(AppStartMetrics.getInstance().appStartContinuousProfiler!!.isRunning) + verify(fixture.logger).log( + eq(SentryLevel.DEBUG), + eq("App start continuous profiling started.") + ) + } + @Test fun `when provider is closed, profiler is stopped`() { val provider = fixture.getSut { config -> - writeConfig(config) + writeConfig(config, continuousProfilingEnabled = false) } provider.shutdown() assertNotNull(AppStartMetrics.getInstance().appStartProfiler) assertFalse(AppStartMetrics.getInstance().appStartProfiler!!.isRunning) } + @Test + fun `when provider is closed, continuous profiler is stopped`() { + val provider = fixture.getSut { config -> + writeConfig(config, profilingEnabled = false) + } + provider.shutdown() + assertNotNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + assertFalse(AppStartMetrics.getInstance().appStartContinuousProfiler!!.isRunning) + } + private fun writeConfig( configFile: File, profilingEnabled: Boolean = true, + continuousProfilingEnabled: Boolean = true, traceSampled: Boolean = true, traceSampleRate: Double = 1.0, profileSampled: Boolean = true, @@ -281,6 +335,7 @@ class SentryPerformanceProviderTest { ) { val appStartProfilingOptions = SentryAppStartProfilingOptions() appStartProfilingOptions.isProfilingEnabled = profilingEnabled + appStartProfilingOptions.isContinuousProfilingEnabled = continuousProfilingEnabled appStartProfilingOptions.isTraceSampled = traceSampled appStartProfilingOptions.traceSampleRate = traceSampleRate appStartProfilingOptions.isProfileSampled = profileSampled diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt index eb0e85dc28e..8d3cf062b20 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt @@ -5,6 +5,7 @@ import android.content.ContentProvider import android.os.Build import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.IContinuousProfiler import io.sentry.ITransactionProfiler import io.sentry.android.core.SentryAndroidOptions import io.sentry.android.core.SentryShadowProcess @@ -56,7 +57,8 @@ class AppStartMetricsTest { metrics.addActivityLifecycleTimeSpans(ActivityLifecycleTimeSpan()) AppStartMetrics.onApplicationCreate(mock()) AppStartMetrics.onContentProviderCreate(mock()) - metrics.setAppStartProfiler(mock()) + metrics.appStartProfiler = mock() + metrics.appStartContinuousProfiler = mock() metrics.appStartSamplingDecision = mock() metrics.clear() @@ -69,6 +71,7 @@ class AppStartMetricsTest { assertTrue(metrics.activityLifecycleTimeSpans.isEmpty()) assertTrue(metrics.contentProviderOnCreateTimeSpans.isEmpty()) assertNull(metrics.appStartProfiler) + assertNull(metrics.appStartContinuousProfiler) assertNull(metrics.appStartSamplingDecision) } @@ -196,6 +199,19 @@ class AppStartMetricsTest { verify(profiler).close() } + @Test + fun `if activity is never started, stops app start continuous profiler if running`() { + val profiler = mock() + whenever(profiler.isRunning).thenReturn(true) + AppStartMetrics.getInstance().appStartContinuousProfiler = profiler + + AppStartMetrics.getInstance().registerApplicationForegroundCheck(mock()) + // Job on main thread checks if activity was launched + Shadows.shadowOf(Looper.getMainLooper()).idle() + + verify(profiler).close() + } + @Test fun `if activity is started, does not stop app start profiler if running`() { val profiler = mock() @@ -210,6 +226,20 @@ class AppStartMetricsTest { verify(profiler, never()).close() } + @Test + fun `if activity is started, does not stop app start continuous profiler if running`() { + val profiler = mock() + whenever(profiler.isRunning).thenReturn(true) + AppStartMetrics.getInstance().appStartContinuousProfiler = profiler + AppStartMetrics.getInstance().onActivityCreated(mock(), mock()) + + AppStartMetrics.getInstance().registerApplicationForegroundCheck(mock()) + // Job on main thread checks if activity was launched + Shadows.shadowOf(Looper.getMainLooper()).idle() + + verify(profiler, never()).close() + } + @Test fun `if app start span is longer than 1 minute, appStartTimeSpanWithFallback returns an empty span`() { val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index ac854110171..fa070ac6447 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -110,7 +110,7 @@ - + diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java index a4a1c5397a9..572c4cdba72 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java @@ -2,12 +2,14 @@ import android.app.Application; import android.os.StrictMode; +import io.sentry.Sentry; /** Apps. main Application. */ public class MyApplication extends Application { @Override public void onCreate() { + Sentry.startProfiler(); strictMode(); super.onCreate(); diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5709c80f667..f3a84217b50 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -619,8 +619,10 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -684,8 +686,10 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -714,7 +718,6 @@ public abstract interface class io/sentry/IContinuousProfiler { public abstract fun close ()V public abstract fun getProfilerId ()Lio/sentry/protocol/SentryId; public abstract fun isRunning ()Z - public abstract fun setScopes (Lio/sentry/IScopes;)V public abstract fun start ()V public abstract fun stop ()V } @@ -921,11 +924,13 @@ public abstract interface class io/sentry/IScopes { public abstract fun setTag (Ljava/lang/String;Ljava/lang/String;)V public abstract fun setTransaction (Ljava/lang/String;)V public abstract fun setUser (Lio/sentry/protocol/User;)V + public abstract fun startProfiler ()V public abstract fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;)Lio/sentry/ITransaction; public abstract fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; public fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ITransaction; public fun startTransaction (Ljava/lang/String;Ljava/lang/String;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public abstract fun stopProfiler ()V public abstract fun withIsolationScope (Lio/sentry/ScopeCallback;)V public abstract fun withScope (Lio/sentry/ScopeCallback;)V } @@ -1395,7 +1400,6 @@ public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfi public static fun getInstance ()Lio/sentry/NoOpContinuousProfiler; public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z - public fun setScopes (Lio/sentry/IScopes;)V public fun start ()V public fun stop ()V } @@ -1465,8 +1469,10 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -1625,8 +1631,10 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -2278,8 +2286,10 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -2343,8 +2353,10 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUser (Lio/sentry/protocol/User;)V + public fun startProfiler ()V public fun startSession ()V public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public fun stopProfiler ()V public fun withIsolationScope (Lio/sentry/ScopeCallback;)V public fun withScope (Lio/sentry/ScopeCallback;)V } @@ -2447,12 +2459,14 @@ public final class io/sentry/Sentry { public static fun setTag (Ljava/lang/String;Ljava/lang/String;)V public static fun setTransaction (Ljava/lang/String;)V public static fun setUser (Lio/sentry/protocol/User;)V + public static fun startProfiler ()V public static fun startSession ()V public static fun startTransaction (Lio/sentry/TransactionContext;)Lio/sentry/ITransaction; public static fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; public static fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ITransaction; public static fun startTransaction (Ljava/lang/String;Ljava/lang/String;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; public static fun startTransaction (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction; + public static fun stopProfiler ()V public static fun withIsolationScope (Lio/sentry/ScopeCallback;)V public static fun withScope (Lio/sentry/ScopeCallback;)V } @@ -2468,10 +2482,12 @@ public final class io/sentry/SentryAppStartProfilingOptions : io/sentry/JsonSeri public fun getProfilingTracesHz ()I public fun getTraceSampleRate ()Ljava/lang/Double; public fun getUnknown ()Ljava/util/Map; + public fun isContinuousProfilingEnabled ()Z public fun isProfileSampled ()Z public fun isProfilingEnabled ()Z public fun isTraceSampled ()Z public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setContinuousProfilingEnabled (Z)V public fun setProfileSampleRate (Ljava/lang/Double;)V public fun setProfileSampled (Z)V public fun setProfilingEnabled (Z)V @@ -2489,6 +2505,7 @@ public final class io/sentry/SentryAppStartProfilingOptions$Deserializer : io/se } public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { + public static final field IS_CONTINUOUS_PROFILING_ENABLED Ljava/lang/String; public static final field IS_PROFILING_ENABLED Ljava/lang/String; public static final field PROFILE_SAMPLED Ljava/lang/String; public static final field PROFILE_SAMPLE_RATE Ljava/lang/String; @@ -2993,6 +3010,7 @@ public class io/sentry/SentryOptions { public fun isAttachServerName ()Z public fun isAttachStacktrace ()Z public fun isAttachThreads ()Z + public fun isContinuousProfilingEnabled ()Z public fun isDebug ()Z public fun isEnableAppStartProfiling ()Z public fun isEnableAutoSessionTracking ()Z diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index d71f5b10ba9..537bcdf1044 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -277,6 +277,16 @@ public boolean isAncestorOf(final @Nullable IScopes otherScopes) { return Sentry.startTransaction(transactionContext, transactionOptions); } + @Override + public void startProfiler() { + Sentry.startProfiler(); + } + + @Override + public void stopProfiler() { + Sentry.stopProfiler(); + } + @Override public @NotNull SentryId captureProfileChunk( final @NotNull ProfileChunk profilingContinuousData) { diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 31c22704311..d6755c2c41d 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -277,6 +277,16 @@ public boolean isAncestorOf(final @Nullable IScopes otherScopes) { return scopes.startTransaction(transactionContext, transactionOptions); } + @Override + public void startProfiler() { + scopes.startProfiler(); + } + + @Override + public void stopProfiler() { + scopes.stopProfiler(); + } + @ApiStatus.Internal @Override public void setSpanContext( diff --git a/sentry/src/main/java/io/sentry/IContinuousProfiler.java b/sentry/src/main/java/io/sentry/IContinuousProfiler.java index 3fe19f614bb..14ce41a8156 100644 --- a/sentry/src/main/java/io/sentry/IContinuousProfiler.java +++ b/sentry/src/main/java/io/sentry/IContinuousProfiler.java @@ -13,8 +13,6 @@ public interface IContinuousProfiler { void stop(); - void setScopes(final @NotNull IScopes scopes); - /** Cancel the profiler and stops it. Used on SDK close. */ void close(); diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 14b8753b285..59a577f04b3 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -592,6 +592,10 @@ ITransaction startTransaction( final @NotNull TransactionContext transactionContext, final @NotNull TransactionOptions transactionOptions); + void startProfiler(); + + void stopProfiler(); + /** * Associates {@link ISpan} and the transaction name with the {@link Throwable}. Used to determine * in which trace the exception has been thrown in framework integrations. diff --git a/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java b/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java index 0ae5d6d81ba..4ccf7cc681c 100644 --- a/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java +++ b/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java @@ -19,9 +19,6 @@ public void start() {} @Override public void stop() {} - @Override - public void setScopes(@NotNull IScopes scopes) {} - @Override public boolean isRunning() { return false; diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index a304619b277..925f1e64eeb 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -243,6 +243,12 @@ public boolean isAncestorOf(@Nullable IScopes otherScopes) { return NoOpTransaction.getInstance(); } + @Override + public void startProfiler() {} + + @Override + public void stopProfiler() {} + @Override public void setSpanContext( final @NotNull Throwable throwable, diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 9ec3db2a62a..11bae042b0e 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -238,6 +238,12 @@ public boolean isAncestorOf(@Nullable IScopes otherScopes) { return NoOpTransaction.getInstance(); } + @Override + public void startProfiler() {} + + @Override + public void stopProfiler() {} + @Override public void setSpanContext( final @NotNull Throwable throwable, diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 33d7f65d600..86f0f5f8bcb 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -923,6 +923,34 @@ public void flush(long timeoutMillis) { return transaction; } + @Override + public void startProfiler() { + if (getOptions().isContinuousProfilingEnabled()) { + getOptions().getLogger().log(SentryLevel.DEBUG, "Started continuous Profiling."); + getOptions().getContinuousProfiler().start(); + } else { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, + "Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it."); + } + } + + @Override + public void stopProfiler() { + if (getOptions().isContinuousProfilingEnabled()) { + getOptions().getLogger().log(SentryLevel.DEBUG, "Stopped continuous Profiling."); + getOptions().getContinuousProfiler().stop(); + } else { + getOptions() + .getLogger() + .log( + SentryLevel.WARNING, + "Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it."); + } + } + @Override @ApiStatus.Internal public void setSpanContext( diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 8da3c4f6158..9944a87a0a8 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -280,6 +280,16 @@ public boolean isAncestorOf(final @Nullable IScopes otherScopes) { return Sentry.startTransaction(transactionContext, transactionOptions); } + @Override + public void startProfiler() { + Sentry.startProfiler(); + } + + @Override + public void stopProfiler() { + Sentry.stopProfiler(); + } + @ApiStatus.Internal @Override public void setSpanContext( diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 7a35720e2dc..3c30597b928 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1049,6 +1049,16 @@ public static void endSession() { return getCurrentScopes().startTransaction(transactionContext, transactionOptions); } + /** Starts the continuous profiler, if enabled. */ + public static void startProfiler() { + getCurrentScopes().startProfiler(); + } + + /** Starts the continuous profiler, if enabled. */ + public static void stopProfiler() { + getCurrentScopes().stopProfiler(); + } + /** * Gets the current active transaction or span. * diff --git a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java index a9828792d77..b0926b9d937 100644 --- a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java +++ b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java @@ -18,6 +18,7 @@ public final class SentryAppStartProfilingOptions implements JsonUnknown, JsonSe @Nullable Double traceSampleRate; @Nullable String profilingTracesDirPath; boolean isProfilingEnabled; + boolean isContinuousProfilingEnabled; int profilingTracesHz; private @Nullable Map unknown; @@ -30,6 +31,7 @@ public SentryAppStartProfilingOptions() { profileSampleRate = null; profilingTracesDirPath = null; isProfilingEnabled = false; + isContinuousProfilingEnabled = false; profilingTracesHz = 0; } @@ -42,6 +44,7 @@ public SentryAppStartProfilingOptions() { profileSampleRate = samplingDecision.getProfileSampleRate(); profilingTracesDirPath = options.getProfilingTracesDirPath(); isProfilingEnabled = options.isProfilingEnabled(); + isContinuousProfilingEnabled = options.isContinuousProfilingEnabled(); profilingTracesHz = options.getProfilingTracesHz(); } @@ -93,6 +96,14 @@ public boolean isProfilingEnabled() { return isProfilingEnabled; } + public void setContinuousProfilingEnabled(final boolean continuousProfilingEnabled) { + isContinuousProfilingEnabled = continuousProfilingEnabled; + } + + public boolean isContinuousProfilingEnabled() { + return isContinuousProfilingEnabled; + } + public void setProfilingTracesHz(final int profilingTracesHz) { this.profilingTracesHz = profilingTracesHz; } @@ -110,6 +121,7 @@ public static final class JsonKeys { public static final String TRACE_SAMPLE_RATE = "trace_sample_rate"; public static final String PROFILING_TRACES_DIR_PATH = "profiling_traces_dir_path"; public static final String IS_PROFILING_ENABLED = "is_profiling_enabled"; + public static final String IS_CONTINUOUS_PROFILING_ENABLED = "is_continuous_profiling_enabled"; public static final String PROFILING_TRACES_HZ = "profiling_traces_hz"; } @@ -123,6 +135,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger writer.name(JsonKeys.TRACE_SAMPLE_RATE).value(logger, traceSampleRate); writer.name(JsonKeys.PROFILING_TRACES_DIR_PATH).value(logger, profilingTracesDirPath); writer.name(JsonKeys.IS_PROFILING_ENABLED).value(logger, isProfilingEnabled); + writer + .name(JsonKeys.IS_CONTINUOUS_PROFILING_ENABLED) + .value(logger, isContinuousProfilingEnabled); writer.name(JsonKeys.PROFILING_TRACES_HZ).value(logger, profilingTracesHz); if (unknown != null) { @@ -195,6 +210,12 @@ public static final class Deserializer options.isProfilingEnabled = isProfilingEnabled; } break; + case JsonKeys.IS_CONTINUOUS_PROFILING_ENABLED: + Boolean isContinuousProfilingEnabled = reader.nextBooleanOrNull(); + if (isContinuousProfilingEnabled != null) { + options.isContinuousProfilingEnabled = isContinuousProfilingEnabled; + } + break; case JsonKeys.PROFILING_TRACES_HZ: Integer profilingTracesHz = reader.nextIntegerOrNull(); if (profilingTracesHz != null) { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index fdf985b1321..9c914eda4fd 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1699,6 +1699,17 @@ public boolean isProfilingEnabled() { || getProfilesSampler() != null; } + /** + * Returns if continuous profiling is enabled. This means that no profile sample rate has been + * set. + * + * @return if continuous profiling is enabled. + */ + @ApiStatus.Internal + public boolean isContinuousProfilingEnabled() { + return getProfilesSampleRate() == null && getProfilesSampler() == null; + } + /** * Returns the callback used to determine if a profile is sampled. * diff --git a/sentry/src/test/java/io/sentry/HubAdapterTest.kt b/sentry/src/test/java/io/sentry/HubAdapterTest.kt index b48ae797035..310c1537099 100644 --- a/sentry/src/test/java/io/sentry/HubAdapterTest.kt +++ b/sentry/src/test/java/io/sentry/HubAdapterTest.kt @@ -265,4 +265,14 @@ class HubAdapterTest { HubAdapter.getInstance().reportFullyDisplayed() verify(scopes).reportFullyDisplayed() } + + @Test fun `startProfiler calls Hub`() { + HubAdapter.getInstance().startProfiler() + verify(scopes).startProfiler() + } + + @Test fun `stopProfiler calls Hub`() { + HubAdapter.getInstance().stopProfiler() + verify(scopes).stopProfiler() + } } diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 001ac46c67c..53dd7d85bfc 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1230,7 +1230,8 @@ class JsonSerializerTest { val actual = serializeToString(appStartProfilingOptions) val expected = "{\"profile_sampled\":true,\"profile_sample_rate\":0.8,\"trace_sampled\":false," + - "\"trace_sample_rate\":0.1,\"profiling_traces_dir_path\":null,\"is_profiling_enabled\":false,\"profiling_traces_hz\":65}" + "\"trace_sample_rate\":0.1,\"profiling_traces_dir_path\":null,\"is_profiling_enabled\":false," + + "\"is_continuous_profiling_enabled\":false,\"profiling_traces_hz\":65}" assertEquals(expected, actual) } @@ -1247,6 +1248,7 @@ class JsonSerializerTest { assertEquals(appStartProfilingOptions.profileSampled, actual.profileSampled) assertEquals(appStartProfilingOptions.profileSampleRate, actual.profileSampleRate) assertEquals(appStartProfilingOptions.isProfilingEnabled, actual.isProfilingEnabled) + assertEquals(appStartProfilingOptions.isContinuousProfilingEnabled, actual.isContinuousProfilingEnabled) assertEquals(appStartProfilingOptions.profilingTracesHz, actual.profilingTracesHz) assertEquals(appStartProfilingOptions.profilingTracesDirPath, actual.profilingTracesDirPath) assertNull(actual.unknown) @@ -1550,6 +1552,7 @@ class JsonSerializerTest { profileSampled = true profileSampleRate = 0.8 isProfilingEnabled = false + isContinuousProfilingEnabled = false profilingTracesHz = 65 } diff --git a/sentry/src/test/java/io/sentry/NoOpHubTest.kt b/sentry/src/test/java/io/sentry/NoOpHubTest.kt index fbd6e4c41b8..e0eb08ded00 100644 --- a/sentry/src/test/java/io/sentry/NoOpHubTest.kt +++ b/sentry/src/test/java/io/sentry/NoOpHubTest.kt @@ -115,4 +115,10 @@ class NoOpHubTest { sut.withScope(scopeCallback) verify(scopeCallback).run(NoOpScope.getInstance()) } + + @Test + fun `startProfiler doesnt throw`() = sut.startProfiler() + + @Test + fun `stopProfiler doesnt throw`() = sut.stopProfiler() } diff --git a/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt b/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt index d30b653456b..9c7418c6b55 100644 --- a/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt @@ -265,4 +265,14 @@ class ScopesAdapterTest { ScopesAdapter.getInstance().reportFullyDisplayed() verify(scopes).reportFullyDisplayed() } + + @Test fun `startProfiler calls Scopes`() { + ScopesAdapter.getInstance().startProfiler() + verify(scopes).startProfiler() + } + + @Test fun `stopProfiler calls Scopes`() { + ScopesAdapter.getInstance().stopProfiler() + verify(scopes).stopProfiler() + } } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 39b03d7ad2d..68a1fee5d6b 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2160,6 +2160,56 @@ class ScopesTest { assertEquals("other.span.origin", transaction.spanContext.origin) } + @Test + fun `startProfiler starts the continuous profiler`() { + val profiler = mock() + val scopes = generateScopes { + it.setContinuousProfiler(profiler) + } + scopes.startProfiler() + verify(profiler).start() + } + + @Test + fun `stopProfiler stops the continuous profiler`() { + val profiler = mock() + val scopes = generateScopes { + it.setContinuousProfiler(profiler) + } + scopes.stopProfiler() + verify(profiler).stop() + } + + @Test + fun `startProfiler logs instructions if continuous profiling is disabled`() { + val profiler = mock() + val logger = mock() + val scopes = generateScopes { + it.setContinuousProfiler(profiler) + it.profilesSampleRate = 1.0 + it.setLogger(logger) + it.isDebug = true + } + scopes.startProfiler() + verify(profiler, never()).start() + verify(logger).log(eq(SentryLevel.WARNING), eq("Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it.")) + } + + @Test + fun `stopProfiler logs instructions if continuous profiling is disabled`() { + val profiler = mock() + val logger = mock() + val scopes = generateScopes { + it.setContinuousProfiler(profiler) + it.profilesSampleRate = 1.0 + it.setLogger(logger) + it.isDebug = true + } + scopes.stopProfiler() + verify(profiler, never()).stop() + verify(logger).log(eq(SentryLevel.WARNING), eq("Continuous Profiling is not enabled. Set profilesSampleRate and profilesSampler to null to enable it.")) + } + private val dsnTest = "https://key@sentry.io/proj" private fun generateScopes(optionsConfiguration: Sentry.OptionsConfiguration? = null): IScopes { diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index f6634b6657d..74ce882fe1e 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -193,42 +193,47 @@ class SentryOptionsTest { } @Test - fun `when options is initialized, isProfilingEnabled is false`() { + fun `when options is initialized, isProfilingEnabled is false and isContinuousProfilingEnabled is true`() { assertFalse(SentryOptions().isProfilingEnabled) + assertTrue(SentryOptions().isContinuousProfilingEnabled) } @Test - fun `when profilesSampleRate is null and profilesSampler is null, isProfilingEnabled is false`() { + fun `when profilesSampleRate is null and profilesSampler is null, isProfilingEnabled is false and isContinuousProfilingEnabled is true`() { val options = SentryOptions().apply { this.profilesSampleRate = null this.profilesSampler = null } assertFalse(options.isProfilingEnabled) + assertTrue(options.isContinuousProfilingEnabled) } @Test - fun `when profilesSampleRate is 0 and profilesSampler is null, isProfilingEnabled is false`() { + fun `when profilesSampleRate is 0 and profilesSampler is null, isProfilingEnabled is false and isContinuousProfilingEnabled is false`() { val options = SentryOptions().apply { this.profilesSampleRate = 0.0 this.profilesSampler = null } assertFalse(options.isProfilingEnabled) + assertFalse(options.isContinuousProfilingEnabled) } @Test - fun `when profilesSampleRate is set to a value higher than 0, isProfilingEnabled is true`() { + fun `when profilesSampleRate is set to a value higher than 0, isProfilingEnabled is true and isContinuousProfilingEnabled is false`() { val options = SentryOptions().apply { this.profilesSampleRate = 0.1 } assertTrue(options.isProfilingEnabled) + assertFalse(options.isContinuousProfilingEnabled) } @Test - fun `when profilesSampler is set to a value, isProfilingEnabled is true`() { + fun `when profilesSampler is set to a value, isProfilingEnabled is true and isContinuousProfilingEnabled is false`() { val options = SentryOptions().apply { this.profilesSampler = SentryOptions.ProfilesSamplerCallback { 1.0 } } assertTrue(options.isProfilingEnabled) + assertFalse(options.isContinuousProfilingEnabled) } @Test diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index 3f39b29c018..6f7cd427daf 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -1277,6 +1277,52 @@ class SentryTest { assertNotSame(s1, s2) } + @Test + fun `startProfiler starts the continuous profiler`() { + val profiler = mock() + Sentry.init { + it.dsn = dsn + it.setContinuousProfiler(profiler) + } + Sentry.startProfiler() + verify(profiler).start() + } + + @Test + fun `startProfiler is ignored when continuous profiling is disabled`() { + val profiler = mock() + Sentry.init { + it.dsn = dsn + it.setContinuousProfiler(profiler) + it.profilesSampleRate = 1.0 + } + Sentry.startProfiler() + verify(profiler, never()).start() + } + + @Test + fun `stopProfiler stops the continuous profiler`() { + val profiler = mock() + Sentry.init { + it.dsn = dsn + it.setContinuousProfiler(profiler) + } + Sentry.stopProfiler() + verify(profiler).stop() + } + + @Test + fun `stopProfiler is ignored when continuous profiling is disabled`() { + val profiler = mock() + Sentry.init { + it.dsn = dsn + it.setContinuousProfiler(profiler) + it.profilesSampleRate = 1.0 + } + Sentry.stopProfiler() + verify(profiler, never()).stop() + } + private class InMemoryOptionsObserver : IOptionsObserver { var release: String? = null private set From 6639591647e4286b72672f8e6d6412ebca88c7b3 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 14 Nov 2024 15:51:19 +0100 Subject: [PATCH 10/10] removed `instrumenter` copy in SpanContext copy constructor --- sentry/src/main/java/io/sentry/SpanContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index 79b3b4abf10..0a9a143616e 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -126,7 +126,6 @@ public SpanContext(final @NotNull SpanContext spanContext) { if (copiedUnknown != null) { this.unknown = copiedUnknown; } - this.instrumenter = spanContext.instrumenter; this.baggage = spanContext.baggage; final Map copiedData = CollectionUtils.newConcurrentHashMap(spanContext.data); if (copiedData != null) {