diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 20ebc767ee..dd4cfdf353 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -37,10 +37,10 @@ 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 public fun stop ()V } @@ -447,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; @@ -465,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 b8c016e4ac..0536dcabc7 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,11 +4,15 @@ import android.annotation.SuppressLint; import android.os.Build; +import io.sentry.CompositePerformanceCollector; import io.sentry.IContinuousProfiler; 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; @@ -28,7 +32,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; @@ -37,7 +40,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 CompositePerformanceCollector performanceCollector; private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; @@ -47,14 +51,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; } @@ -65,10 +67,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, @@ -92,11 +90,14 @@ private void init() { logger); } - public synchronized void setScopes(final @NotNull IScopes scopes) { - this.scopes = scopes; - } - 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; @@ -124,8 +125,12 @@ public synchronized void start() { chunkId = new SentryId(); } + if (performanceCollector != null) { + performanceCollector.start(chunkId.toString()); + } + try { - closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); + stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); } catch (RejectedExecutionException e) { logger.log( SentryLevel.ERROR, @@ -140,8 +145,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) { @@ -154,8 +159,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) { @@ -195,6 +205,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 @@ -224,7 +239,7 @@ public boolean isRunning() { @VisibleForTesting @Nullable - Future getCloseFuture() { - return closeFuture; + Future getStopFuture() { + return stopFuture; } } 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 62900c25b8..605294f666 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,11 +6,14 @@ import android.content.Context; import android.content.pm.PackageInfo; import io.sentry.DeduplicateMultithreadedEventProcessor; -import io.sentry.DefaultTransactionPerformanceCollector; +import io.sentry.DefaultCompositePerformanceCollector; +import io.sentry.IContinuousProfiler; import io.sentry.ILogger; import io.sentry.ISentryLifecycleToken; 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; @@ -159,23 +162,23 @@ static void initializeIntegrationsAndProcessors( // 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()) { - 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"))); - } + 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())); @@ -223,7 +226,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()) { @@ -233,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 64a1ceda60..1d76775b3f 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/internal/util/AndroidThreadChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java index 15781d711f..ccd4a92b27 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/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 996c6ab171..f43b44a5ad 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 5878354e70..70ed75186c 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,25 @@ 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 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 import io.sentry.TransactionContext 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 +36,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 @@ -70,10 +79,9 @@ class AndroidContinuousProfilerTest { frameMetricsCollector, options.logger, options.profilingTracesDirPath, - options.isProfilingEnabled, options.profilingTracesHz, options.executorService - ).also { it.setScopes(scopes) } + ) } } @@ -111,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 @@ -149,26 +159,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 @@ -269,6 +265,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.compositePerformanceCollector = 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.compositePerformanceCollector = 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() @@ -294,9 +311,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 @@ -333,6 +350,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(2, 3, SentryNanotimeDate())) + collectionData.addCpuData(CpuCollectionData(3.0, SentryNanotimeDate())) + whenever(performanceCollector.stop(any())).thenReturn(listOf(collectionData)) + + fixture.options.compositePerformanceCollector = 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-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 95ca59a06e..56571b4431 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,14 +6,19 @@ 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.IContinuousProfiler import io.sentry.ILogger +import io.sentry.ITransactionProfiler 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 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 @@ -35,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 @@ -344,11 +350,113 @@ 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 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 = { + profilesSampler = mock() + }) + + assertNotNull(fixture.sentryOptions.transactionProfiler) + assertTrue(fixture.sentryOptions.transactionProfiler is AndroidTransactionProfiler) + 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 @@ -663,10 +771,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-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt index 26a76af30e..237bc54867 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/internal/util/AndroidThreadCheckerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/AndroidThreadCheckerTest.kt index eb59f0732e..0b3729f8ce 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-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 eb0e85dc28..8d3cf062b2 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-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 17c37d69bf..b292f0d038 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-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api b/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api index bb749a7df1..80e3c55d1c 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 7869f6dc96..19de213e18 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-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index ac85411017..fa070ac644 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 a4a1c5397a..572c4cdba7 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-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 36a5ccf1e6..06b7a6f8d4 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 67b2c021f8..7711541cef 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 babc5d1db4..f3a84217b5 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 { @@ -607,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 } @@ -672,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 } @@ -700,8 +716,8 @@ 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 public abstract fun stop ()V } @@ -908,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 } @@ -1018,7 +1036,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 +1376,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,8 +1398,8 @@ 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 public fun stop ()V } @@ -1440,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 } @@ -1600,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 } @@ -1661,7 +1694,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 +1751,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 +1884,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 @@ -2262,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 } @@ -2327,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 } @@ -2431,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 } @@ -2452,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 @@ -2473,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; @@ -2899,9 +2932,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 +3003,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; @@ -2976,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 @@ -3012,8 +3047,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 +3129,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 +3615,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 +3799,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 +6370,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 +6380,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 +6390,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/TransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/CompositePerformanceCollector.java similarity index 61% rename from sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/CompositePerformanceCollector.java index 7880d61197..e6238679a7 100644 --- a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/CompositePerformanceCollector.java @@ -5,10 +5,14 @@ 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); + /** 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/main/java/io/sentry/DefaultTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java similarity index 87% rename from sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java index 9489842b5c..ae99fe00c7 100644 --- a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java @@ -15,8 +15,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 AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock(); @@ -31,7 +30,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<>(); @@ -82,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)) { try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer == null) { @@ -110,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; @@ -157,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/DefaultSpanFactory.java b/sentry/src/main/java/io/sentry/DefaultSpanFactory.java index 7ac2448849..6054ed5166 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/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index d71f5b10ba..537bcdf104 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 31c2270431..d6755c2c41 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 c94eb9bba3..14ce41a815 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; @@ -12,8 +13,9 @@ public interface IContinuousProfiler { void stop(); - void setScopes(final @NotNull IScopes scopes); - /** Cancel the profiler and stops it. Used on SDK close. */ void close(); + + @NotNull + SentryId getProfilerId(); } diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 14b8753b28..59a577f04b 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/ISpanFactory.java b/sentry/src/main/java/io/sentry/ISpanFactory.java index 1e429e2fea..9b7c6afaba 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 51% rename from sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java rename to sentry/src/main/java/io/sentry/NoOpCompositePerformanceCollector.java index abf5ec5f6a..a159be9182 100644 --- a/sentry/src/main/java/io/sentry/NoOpTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/NoOpCompositePerformanceCollector.java @@ -4,20 +4,23 @@ 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) {} + @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/NoOpContinuousProfiler.java b/sentry/src/main/java/io/sentry/NoOpContinuousProfiler.java index b17123029f..4ccf7cc681 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 { @@ -18,9 +19,6 @@ public void start() {} @Override public void stop() {} - @Override - public void setScopes(@NotNull IScopes scopes) {} - @Override public boolean isRunning() { return false; @@ -28,4 +26,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/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index a304619b27..925f1e64ee 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 9ec3db2a62..11bae042b0 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/NoOpSpanFactory.java b/sentry/src/main/java/io/sentry/NoOpSpanFactory.java index 05bea4edfe..871e281054 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/ProfileChunk.java b/sentry/src/main/java/io/sentry/ProfileChunk.java index 44cb921209..725c151dbd 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/ProfileContext.java b/sentry/src/main/java/io/sentry/ProfileContext.java index 3a7fa9af2f..e4b411c279 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/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 4b7478a3f4..86f0f5f8bc 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -26,7 +26,7 @@ public final class Scopes implements IScopes { private final @Nullable Scopes parentScopes; private final @NotNull String creator; - private final @NotNull TransactionPerformanceCollector transactionPerformanceCollector; + private final @NotNull CompositePerformanceCollector compositePerformanceCollector; private final @NotNull CombinedScopeView combinedScope; @@ -53,7 +53,7 @@ private Scopes( final @NotNull SentryOptions options = getOptions(); validateOptions(options); - this.transactionPerformanceCollector = options.getTransactionPerformanceCollector(); + this.compositePerformanceCollector = options.getCompositePerformanceCollector(); } public @NotNull String getCreator() { @@ -404,7 +404,8 @@ public void close(final boolean isRestarting) { configureScope(scope -> scope.clear()); configureScope(ScopeType.ISOLATION, scope -> scope.clear()); getOptions().getTransactionProfiler().close(); - getOptions().getTransactionPerformanceCollector().close(); + getOptions().getContinuousProfiler().close(); + getOptions().getCompositePerformanceCollector().close(); final @NotNull ISentryExecutorService executorService = getOptions().getExecutorService(); if (isRestarting) { executorService.submit( @@ -897,10 +898,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 @@ -922,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 8da3c4f615..9944a87a0a 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 7a35720e2d..3c30597b92 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 a9828792d7..b0926b9d93 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 c02765fd54..9c914eda4f 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -353,9 +353,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. */ @@ -427,8 +430,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; @@ -1664,6 +1667,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. * @@ -1674,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. * @@ -1997,24 +2033,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 65901a2e1a..943dc349f2 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.util.AutoClosableReentrantLock; 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; @@ -50,7 +51,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) { @@ -68,7 +69,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"); @@ -77,7 +78,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; @@ -87,10 +88,16 @@ 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) { - transactionPerformanceCollector.start(this); + if (compositePerformanceCollector != null) { + compositePerformanceCollector.start(this); } if (transactionOptions.getIdleTimeout() != null @@ -220,8 +227,8 @@ public void finish( finishedCallback.execute(this); } - if (transactionPerformanceCollector != null) { - performanceCollectionData.set(transactionPerformanceCollector.stop(this)); + if (compositePerformanceCollector != null) { + performanceCollectionData.set(compositePerformanceCollector.stop(this)); } }); @@ -469,8 +476,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) { @@ -495,8 +502,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) { @@ -513,16 +520,17 @@ private ISpan createChild( // } // }); // span.setDescription(description); - final long threadId = scopes.getOptions().getThreadChecker().currentThreadSystemId(); - span.setData(SpanDataConvention.THREAD_ID, String.valueOf(threadId)); + 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_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 (transactionPerformanceCollector != null) { - transactionPerformanceCollector.onSpanStarted(span); + if (compositePerformanceCollector != null) { + compositePerformanceCollector.onSpanStarted(span); } return span; } else { diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index f012233504..0a9a143616 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -4,6 +4,7 @@ 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; @@ -95,6 +96,11 @@ public SpanContext( this.description = description; this.status = status; this.origin = origin; + final IThreadChecker threadChecker = + ScopesAdapter.getInstance().getOptions().getThreadChecker(); + this.data.put( + SpanDataConvention.THREAD_ID, String.valueOf(threadChecker.currentThreadSystemId())); + this.data.put(SpanDataConvention.THREAD_NAME, threadChecker.getCurrentThreadName()); } /** @@ -120,6 +126,11 @@ public SpanContext(final @NotNull SpanContext spanContext) { if (copiedUnknown != null) { this.unknown = copiedUnknown; } + this.baggage = spanContext.baggage; + final Map copiedData = CollectionUtils.newConcurrentHashMap(spanContext.data); + if (copiedData != null) { + this.data = copiedData; + } } 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 ffe2414af3..c4329f6dca 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/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index 684001843a..d2a9e5140b 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -78,7 +78,7 @@ public SentryTransaction(final @NotNull SentryTracer sentryTracer) { final SpanContext tracerContext = sentryTracer.getSpanContext(); Map data = sentryTracer.getData(); // tags must be placed on the root of the transaction instead of contexts.trace.tags - final SpanContext tracerContextToSend = + final @NotNull SpanContext tracerContextToSend = new SpanContext( tracerContext.getTraceId(), tracerContext.getSpanId(), 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 81af056e71..deea360f8c 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 b1497d17e7..f80a996785 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 bfa8aac139..2f9b6fc1d2 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 db113fa009..20d6b69367 100644 --- a/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/CheckInSerializationTest.kt @@ -28,7 +28,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" diff --git a/sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt b/sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt similarity index 83% rename from sentry/src/test/java/io/sentry/DefaultTransactionPerformanceCollectorTest.kt rename to sentry/src/test/java/io/sentry/DefaultCompositePerformanceCollectorTest.kt index 60005935c9..fe9dd6039d 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() @@ -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 @@ -50,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) { @@ -61,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) @@ -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/HubAdapterTest.kt b/sentry/src/test/java/io/sentry/HubAdapterTest.kt index b48ae79703..310c153709 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 2128797261..53dd7d85bf 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1068,6 +1068,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") @@ -1101,6 +1102,9 @@ class JsonSerializerTest { assertEquals("dataValue", (jsonTrace["data"] as Map<*, *>)["dataKey"] as String) 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) @@ -1163,7 +1167,7 @@ class JsonSerializerTest { assertEquals("0a53026963414893", transaction.contexts.trace!!.spanId.toString()) assertEquals("http", transaction.contexts.trace!!.operation) assertNotNull(transaction.contexts["custom"]) - assertEquals("transactionDataValue", transaction.contexts.trace!!.data!!["transactionDataKey"]) + assertEquals("transactionDataValue", transaction.contexts.trace!!.data["transactionDataKey"]) assertEquals("some-value", (transaction.contexts["custom"] as Map<*, *>)["some-key"]) assertEquals("extraValue", transaction.getExtra("extraKey")) @@ -1226,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) } @@ -1243,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) @@ -1546,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/NoOpContinuousProfilerTest.kt b/sentry/src/test/java/io/sentry/NoOpContinuousProfilerTest.kt index afbce4a8cb..e791651aef 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/NoOpHubTest.kt b/sentry/src/test/java/io/sentry/NoOpHubTest.kt index fbd6e4c41b..e0eb08ded0 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/OutboxSenderTest.kt b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt index 8a1850e7dd..c8039551ff 100644 --- a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt +++ b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt @@ -38,6 +38,7 @@ class OutboxSenderTest { whenever(options.dsn).thenReturn("https://key@sentry.io/proj") whenever(options.dateProvider).thenReturn(SentryNanotimeDateProvider()) whenever(options.threadChecker).thenReturn(NoOpThreadChecker.getInstance()) + whenever(options.continuousProfiler).thenReturn(NoOpContinuousProfiler.getInstance()) whenever(scopes.options).thenReturn(this.options) } diff --git a/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt b/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt index d30b653456..9c7418c6b5 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 04473ddf7a..68a1fee5d6 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -1795,18 +1795,21 @@ class ScopesTest { fun `Scopes should close the sentry executor processor, profiler and performance collector on close call`() { val executor = mock() val profiler = mock() - val performanceCollector = mock() + val continuousProfiler = 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) sut.close() verify(executor).close(any()) verify(profiler).close() + verify(continuousProfiler).close() verify(performanceCollector).close() } @@ -2157,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 21f33383e7..74ce882fe1 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 @@ -250,8 +255,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 @@ -259,6 +264,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()) @@ -466,16 +476,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/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index 5c707a9893..6f7cd427da 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 @@ -1328,6 +1374,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 3d5eca7f48..2be549cba6 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) @@ -209,6 +213,65 @@ 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 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() @@ -1026,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 @@ -1208,11 +1271,13 @@ 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) {} 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 = { @@ -1363,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 @@ -1376,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/SpanContextTest.kt b/sentry/src/test/java/io/sentry/SpanContextTest.kt index 5e7ba9de25..47b98d5ee8 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") diff --git a/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SpanContextSerializationTest.kt index 707daa78f1..2ebc830a5e 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 setData("spanContextDataKey", "spanContextDataValue") } } 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 26de021fbd..12b1e34827 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 8c39685878..c2bff2a050 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 8281ca67ab..395bb03bba 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 a6f35b31a6..e8df6a21c1 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -123,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 d2d1fd0088..63ae8f03cf 100644 --- a/sentry/src/test/resources/json/sentry_base_event.json +++ b/sentry/src/test/resources/json/sentry_base_event.json @@ -126,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 4ce74eaf09..2079b424cb 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 @@ -126,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 6d421fc993..c6f8dd68b0 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -261,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 d3970bf5b0..7bd64037d7 100644 --- a/sentry/src/test/resources/json/sentry_replay_event.json +++ b/sentry/src/test/resources/json/sentry_replay_event.json @@ -144,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 33080c9686..daa6d025e9 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -183,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 0d6ed5eb09..316b44bbaa 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 @@ -183,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 2d965be1b9..cf927b322b 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 @@ -153,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 c55841a391..edff574fa4 100644 --- a/sentry/src/test/resources/json/span_context.json +++ b/sentry/src/test/resources/json/span_context.json @@ -14,6 +14,8 @@ }, "data": { - "spanContextDataKey": "spanContextDataValue" + "spanContextDataKey": "spanContextDataValue", + "thread.name": "test", + "thread.id": 10 } }