From 50505efcf17872c568634258098b6aa2ec7b9d94 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 13 Jan 2025 19:49:28 +0100 Subject: [PATCH 1/6] Set continuousProfilesSampleRate and startProfiler() and stopProfiler() as experimental --- sentry/src/main/java/io/sentry/Sentry.java | 4 +++- sentry/src/main/java/io/sentry/SentryOptions.java | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 383ba50e84..85ea2bc224 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1051,11 +1051,13 @@ public static void endSession() { } /** Starts the continuous profiler, if enabled. */ + @ApiStatus.Experimental public static void startProfiler() { getCurrentScopes().startProfiler(); } - /** Starts the continuous profiler, if enabled. */ + /** Stops the continuous profiler, if enabled. */ + @ApiStatus.Experimental public static void stopProfiler() { getCurrentScopes().stopProfiler(); } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 8418d4cea4..ddcf8b12b0 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1713,6 +1713,7 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact * * @return the continuous profiler. */ + @ApiStatus.Experimental public @NotNull IContinuousProfiler getContinuousProfiler() { return continuousProfiler; } @@ -1722,6 +1723,7 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact * * @param continuousProfiler - the continuous profiler */ + @ApiStatus.Experimental 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() @@ -1803,10 +1805,12 @@ public void setProfilesSampleRate(final @Nullable Double profilesSampleRate) { * * @return the sample rate */ + @ApiStatus.Experimental public double getContinuousProfilesSampleRate() { return continuousProfilesSampleRate; } + @ApiStatus.Experimental public void setContinuousProfilesSampleRate(final double continuousProfilesSampleRate) { if (!SampleRateUtils.isValidContinuousProfilesSampleRate(continuousProfilesSampleRate)) { throw new IllegalArgumentException( From def8f0c402b7084b664cd1428819e039eaba6752 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 20 Jan 2025 12:45:22 +0100 Subject: [PATCH 2/6] Added chunk start timestamp to ProfileChunk --- .../core/AndroidContinuousProfiler.java | 13 +++++++++-- sentry/api/sentry.api | 6 +++-- .../src/main/java/io/sentry/ProfileChunk.java | 23 +++++++++++++++++-- .../test/java/io/sentry/JsonSerializerTest.kt | 5 +++- .../test/java/io/sentry/SentryClientTest.kt | 2 +- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 99c042e015..574adf8cef 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 @@ -16,7 +16,9 @@ import io.sentry.PerformanceCollectionData; import io.sentry.ProfileChunk; import io.sentry.Sentry; +import io.sentry.SentryDate; import io.sentry.SentryLevel; +import io.sentry.SentryNanotimeDate; import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.SentryId; @@ -52,6 +54,7 @@ public class AndroidContinuousProfiler private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); + private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate(); public AndroidContinuousProfiler( final @NotNull BuildInfoProvider buildInfoProvider, @@ -138,8 +141,10 @@ public synchronized void start() { stop(); return; } + startProfileChunkTimestamp = scopes.getOptions().getDateProvider().now(); + } else { + startProfileChunkTimestamp = new SentryNanotimeDate(); } - final AndroidProfiler.ProfileStartData startData = profiler.start(); // check if profiling started if (startData == null) { @@ -213,7 +218,11 @@ private synchronized void stop(final boolean restartProfiler) { synchronized (payloadBuilders) { payloadBuilders.add( new ProfileChunk.Builder( - profilerId, chunkId, endData.measurementsMap, endData.traceFile)); + profilerId, + chunkId, + endData.measurementsMap, + endData.traceFile, + startProfileChunkTimestamp)); } } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a097588a7c..e8ea8ec7c0 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1846,7 +1846,7 @@ public final class io/sentry/PerformanceCollectionData { public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun ()V - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/io/File;Ljava/util/Map;Lio/sentry/SentryOptions;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/io/File;Ljava/util/Map;Ljava/lang/Double;Lio/sentry/SentryOptions;)V public fun equals (Ljava/lang/Object;)Z public fun getChunkId ()Lio/sentry/protocol/SentryId; public fun getClientSdk ()Lio/sentry/protocol/SdkVersion; @@ -1857,6 +1857,7 @@ public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentr public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun getRelease ()Ljava/lang/String; public fun getSampledProfile ()Ljava/lang/String; + public fun getTimestamp ()D public fun getTraceFile ()Ljava/io/File; public fun getUnknown ()Ljava/util/Map; public fun getVersion ()Ljava/lang/String; @@ -1868,7 +1869,7 @@ public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentr } public final class io/sentry/ProfileChunk$Builder { - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/util/Map;Ljava/io/File;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/util/Map;Ljava/io/File;Lio/sentry/SentryDate;)V public fun build (Lio/sentry/SentryOptions;)Lio/sentry/ProfileChunk; } @@ -1888,6 +1889,7 @@ public final class io/sentry/ProfileChunk$JsonKeys { public static final field PROFILER_ID Ljava/lang/String; public static final field RELEASE Ljava/lang/String; public static final field SAMPLED_PROFILE Ljava/lang/String; + public static final field TIMESTAMP Ljava/lang/String; public static final field VERSION Ljava/lang/String; public fun ()V } diff --git a/sentry/src/main/java/io/sentry/ProfileChunk.java b/sentry/src/main/java/io/sentry/ProfileChunk.java index 725c151dbd..89d9293f5c 100644 --- a/sentry/src/main/java/io/sentry/ProfileChunk.java +++ b/sentry/src/main/java/io/sentry/ProfileChunk.java @@ -26,6 +26,7 @@ public final class ProfileChunk implements JsonUnknown, JsonSerializable { private @NotNull String release; private @Nullable String environment; private @NotNull String version; + private double timestamp; private final @NotNull File traceFile; @@ -40,6 +41,7 @@ public ProfileChunk() { SentryId.EMPTY_ID, new File("dummy"), new HashMap<>(), + 0.0, SentryOptions.empty()); } @@ -48,6 +50,7 @@ public ProfileChunk( final @NotNull SentryId chunkId, final @NotNull File traceFile, final @NotNull Map measurements, + final @NotNull Double timestamp, final @NotNull SentryOptions options) { this.profilerId = profilerId; this.chunkId = chunkId; @@ -59,6 +62,7 @@ public ProfileChunk( this.environment = options.getEnvironment(); this.platform = "android"; this.version = "2"; + this.timestamp = timestamp; } public @NotNull Map getMeasurements() { @@ -109,6 +113,10 @@ public void setSampledProfile(final @Nullable String sampledProfile) { return traceFile; } + public double getTimestamp() { + return timestamp; + } + public @NotNull String getVersion() { return version; } @@ -152,20 +160,23 @@ public static final class Builder { private final @NotNull SentryId chunkId; private final @NotNull Map measurements; private final @NotNull File traceFile; + private final double timestamp; public Builder( final @NotNull SentryId profilerId, final @NotNull SentryId chunkId, final @NotNull Map measurements, - final @NotNull File traceFile) { + final @NotNull File traceFile, + final @NotNull SentryDate timestamp) { this.profilerId = profilerId; this.chunkId = chunkId; this.measurements = new ConcurrentHashMap<>(measurements); this.traceFile = traceFile; + this.timestamp = DateUtils.nanosToSeconds(timestamp.nanoTimestamp()); } public ProfileChunk build(SentryOptions options) { - return new ProfileChunk(profilerId, chunkId, traceFile, measurements, options); + return new ProfileChunk(profilerId, chunkId, traceFile, measurements, timestamp, options); } } @@ -182,6 +193,7 @@ public static final class JsonKeys { public static final String ENVIRONMENT = "environment"; public static final String VERSION = "version"; public static final String SAMPLED_PROFILE = "sampled_profile"; + public static final String TIMESTAMP = "timestamp"; } @Override @@ -208,6 +220,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (sampledProfile != null) { writer.name(JsonKeys.SAMPLED_PROFILE).value(logger, sampledProfile); } + writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -301,6 +314,12 @@ public static final class Deserializer implements JsonDeserializer data.sampledProfile = sampledProfile; } break; + case JsonKeys.TIMESTAMP: + Double timestamp = reader.nextDoubleOrNull(); + if (timestamp != null) { + data.timestamp = timestamp; + } + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 7b8fc219b9..0b63116ee6 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -885,7 +885,7 @@ class JsonSerializerTest { fixture.options.sdkVersion = SdkVersion("test", "1.2.3") fixture.options.release = "release" fixture.options.environment = "environment" - val profileChunk = ProfileChunk(profilerId, chunkId, fixture.traceFile, HashMap(), fixture.options) + val profileChunk = ProfileChunk(profilerId, chunkId, fixture.traceFile, HashMap(), 5.3, fixture.options) val measurementNow = SentryNanotimeDate() val measurementNowSeconds = BigDecimal.valueOf(DateUtils.nanosToSeconds(measurementNow.nanoTimestamp())).setScale(6, RoundingMode.DOWN) @@ -928,6 +928,7 @@ class JsonSerializerTest { assertEquals("release", element["release"] as String) assertEquals(mapOf("name" to "test", "version" to "1.2.3"), element["client_sdk"] as Map) assertEquals("2", element["version"] as String) + assertEquals(5.3, element["timestamp"] as Double) assertEquals("sampled profile in base 64", element["sampled_profile"] as String) assertEquals( mapOf( @@ -992,6 +993,7 @@ class JsonSerializerTest { "profiler_id":"$profilerId", "release":"release", "sampled_profile":"sampled profile in base 64", + "timestamp":"5.3", "version":"2", "measurements":{ "screen_frame_rates": { @@ -1035,6 +1037,7 @@ class JsonSerializerTest { assertEquals(profilerId, profileChunk.profilerId) assertEquals("release", profileChunk.release) assertEquals("sampled profile in base 64", profileChunk.sampledProfile) + assertEquals(5.3, profileChunk.timestamp) assertEquals("2", profileChunk.version) val expectedMeasurements = mapOf( ProfileMeasurement.ID_SCREEN_FRAME_RATES to ProfileMeasurement( diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index fc54dcb1bd..563e0725f0 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -98,7 +98,7 @@ class SentryClientTest { whenever(scopes.options).thenReturn(sentryOptions) sentryTracer = SentryTracer(TransactionContext("a-transaction", "op", TracesSamplingDecision(true)), scopes) sentryTracer.startChild("a-span", "span 1").finish() - profileChunk = ProfileChunk(SentryId(), SentryId(), profilingTraceFile, emptyMap(), sentryOptions) + profileChunk = ProfileChunk(SentryId(), SentryId(), profilingTraceFile, emptyMap(), 1.0, sentryOptions) } var attachment = Attachment("hello".toByteArray(), "hello.txt", "text/plain", true) From 6c07e338576a9ccab8a04b657c70ba3c277bd016 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 21 Jan 2025 14:03:44 +0100 Subject: [PATCH 3/6] updated changelog --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdbdbc0d7e..3cea9e2836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## Unreleased + +### Features + +- Add Continuous Profiling Support ([#3710](https://github.com/getsentry/sentry-java/pull/3710)) + + To enable Continuous Profiling use the `Sentry.startProfiler` and `Sentry.stopProfiler` experimental APIs. Sampling rate can be set through `options.continuousProfilesSampleRate` (defaults to 1.0). + Note: Both `options.profilesSampler` and `options.profilesSampleRate` must not be set to enable Continuous Profiling. + + ```kotlin + import io.sentry.android.core.SentryAndroid + + SentryAndroid.init(context) { options -> + + // Currently under experimental options: + options.continuousProfilesSampleRate = 1.0 + } + // Start profiling + Sentry.startProfiler() + + // After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped. + Sentry.stopProfiler() + ``` + + To learn more visit [Sentry's Continuous Profiling](https://docs.sentry.io/product/explore/profiling/transaction-vs-continuous-profiling/#continuous-profiling-mode) documentation page. + ## 8.0.0-beta.3 ### Features From 6eeed8ef9583bc63e49abfef7fe39c272b4081c7 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 24 Jan 2025 09:49:29 +0100 Subject: [PATCH 4/6] Moved setContinuousProfilesSampleRate into ExperimentalOptions --- CHANGELOG.md | 16 +++++++++++- .../android/core/ManifestMetadataReader.java | 2 +- .../core/ManifestMetadataReaderTest.kt | 2 +- sentry/api/sentry.api | 3 ++- .../java/io/sentry/ExperimentalOptions.java | 26 +++++++++++++++++++ .../main/java/io/sentry/SentryOptions.java | 26 +++---------------- sentry/src/test/java/io/sentry/ScopesTest.kt | 2 +- .../test/java/io/sentry/SentryOptionsTest.kt | 12 ++++----- sentry/src/test/java/io/sentry/SentryTest.kt | 6 ++--- .../test/java/io/sentry/TracesSamplerTest.kt | 2 +- 10 files changed, 59 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cea9e2836..a1d30f3de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,27 @@ To enable Continuous Profiling use the `Sentry.startProfiler` and `Sentry.stopProfiler` experimental APIs. Sampling rate can be set through `options.continuousProfilesSampleRate` (defaults to 1.0). Note: Both `options.profilesSampler` and `options.profilesSampleRate` must not be set to enable Continuous Profiling. + ```java + import io.sentry.android.core.SentryAndroid; + + SentryAndroid.init(context) { options -> + + // Currently under experimental options: + options.getExperimental().setContinuousProfilesSampleRate(1.0); + } + // Start profiling + Sentry.startProfiler(); + + // After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped. + Sentry.stopProfiler(); + ``` ```kotlin import io.sentry.android.core.SentryAndroid SentryAndroid.init(context) { options -> // Currently under experimental options: - options.continuousProfilesSampleRate = 1.0 + options.experimental.continuousProfilesSampleRate = 1.0 } // Start profiling Sentry.startProfiler() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index f49291340f..70085b2356 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -322,7 +322,7 @@ static void applyMetadata( final double continuousProfilesSampleRate = readDouble(metadata, logger, CONTINUOUS_PROFILES_SAMPLE_RATE); if (continuousProfilesSampleRate != -1) { - options.setContinuousProfilesSampleRate(continuousProfilesSampleRate); + options.getExperimental().setContinuousProfilesSampleRate(continuousProfilesSampleRate); } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index 20900ea133..e1f08b396e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -818,7 +818,7 @@ class ManifestMetadataReaderTest { fun `applyMetadata does not override continuousProfilesSampleRate from options`() { // Arrange val expectedSampleRate = 0.99f - fixture.options.continuousProfilesSampleRate = expectedSampleRate.toDouble() + fixture.options.experimental.continuousProfilesSampleRate = expectedSampleRate.toDouble() val bundle = bundleOf(ManifestMetadataReader.CONTINUOUS_PROFILES_SAMPLE_RATE to 0.1f) val context = fixture.getContext(metaData = bundle) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index e8ea8ec7c0..4d93e217f8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -443,7 +443,9 @@ public abstract interface class io/sentry/EventProcessor { public final class io/sentry/ExperimentalOptions { public fun (Z)V + public fun getContinuousProfilesSampleRate ()D public fun getSessionReplay ()Lio/sentry/SentryReplayOptions; + public fun setContinuousProfilesSampleRate (D)V public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V } @@ -3068,7 +3070,6 @@ public class io/sentry/SentryOptions { public fun setConnectionStatusProvider (Lio/sentry/IConnectionStatusProvider;)V public fun setConnectionTimeoutMillis (I)V public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V - public fun setContinuousProfilesSampleRate (D)V public fun setCron (Lio/sentry/SentryOptions$Cron;)V public fun setDateProvider (Lio/sentry/SentryDateProvider;)V public fun setDebug (Z)V diff --git a/sentry/src/main/java/io/sentry/ExperimentalOptions.java b/sentry/src/main/java/io/sentry/ExperimentalOptions.java index 4a0e7de78d..1a473d7c46 100644 --- a/sentry/src/main/java/io/sentry/ExperimentalOptions.java +++ b/sentry/src/main/java/io/sentry/ExperimentalOptions.java @@ -1,5 +1,7 @@ package io.sentry; +import io.sentry.util.SampleRateUtils; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -11,6 +13,15 @@ public final class ExperimentalOptions { private @NotNull SentryReplayOptions sessionReplay; + /** + * Configures the continuous profiling sample rate as a percentage of profiles to be sent in the + * range of 0.0 to 1.0. if 1.0 is set it means that 100% of profiles will be sent. If set to 0.1 + * only 10% of profiles will be sent. Profiles are picked randomly. Default is 1 (100%). + * ProfilesSampleRate takes precedence over this. To enable continuous profiling, don't set + * profilesSampleRate or profilesSampler, or set them to null. + */ + private double continuousProfilesSampleRate = 1.0; + public ExperimentalOptions(final boolean empty) { this.sessionReplay = new SentryReplayOptions(empty); } @@ -23,4 +34,19 @@ public SentryReplayOptions getSessionReplay() { public void setSessionReplay(final @NotNull SentryReplayOptions sessionReplayOptions) { this.sessionReplay = sessionReplayOptions; } + + public double getContinuousProfilesSampleRate() { + return continuousProfilesSampleRate; + } + + @ApiStatus.Experimental + public void setContinuousProfilesSampleRate(final double continuousProfilesSampleRate) { + if (!SampleRateUtils.isValidContinuousProfilesSampleRate(continuousProfilesSampleRate)) { + throw new IllegalArgumentException( + "The value " + + continuousProfilesSampleRate + + " is not valid. Use values between 0.0 and 1.0."); + } + this.continuousProfilesSampleRate = continuousProfilesSampleRate; + } } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index ddcf8b12b0..ff42854431 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -357,15 +357,6 @@ public class SentryOptions { */ private @Nullable ProfilesSamplerCallback profilesSampler; - /** - * Configures the continuous profiling sample rate as a percentage of profiles to be sent in the - * range of 0.0 to 1.0. if 1.0 is set it means that 100% of profiles will be sent. If set to 0.1 - * only 10% of profiles will be sent. Profiles are picked randomly. Default is 1 (100%). - * ProfilesSampleRate takes precedence over this. To enable continuous profiling, don't set - * profilesSampleRate or profilesSampler, or set them to null. - */ - private double continuousProfilesSampleRate = 1.0; - /** Max trace file size in bytes. */ private long maxTraceFileSize = 5 * 1024 * 1024; @@ -1751,7 +1742,7 @@ public boolean isProfilingEnabled() { public boolean isContinuousProfilingEnabled() { return profilesSampleRate == null && profilesSampler == null - && continuousProfilesSampleRate > 0; + && experimental.getContinuousProfilesSampleRate() > 0; } /** @@ -1807,18 +1798,7 @@ public void setProfilesSampleRate(final @Nullable Double profilesSampleRate) { */ @ApiStatus.Experimental public double getContinuousProfilesSampleRate() { - return continuousProfilesSampleRate; - } - - @ApiStatus.Experimental - public void setContinuousProfilesSampleRate(final double continuousProfilesSampleRate) { - if (!SampleRateUtils.isValidContinuousProfilesSampleRate(continuousProfilesSampleRate)) { - throw new IllegalArgumentException( - "The value " - + continuousProfilesSampleRate - + " is not valid. Use values between 0.0 and 1.0."); - } - this.continuousProfilesSampleRate = continuousProfilesSampleRate; + return experimental.getContinuousProfilesSampleRate(); } /** @@ -2748,7 +2728,7 @@ public void merge(final @NotNull ExternalOptions options) { setProfilesSampleRate(options.getProfilesSampleRate()); } if (options.getContinuousProfilesSampleRate() != null) { - setContinuousProfilesSampleRate(options.getContinuousProfilesSampleRate()); + experimental.setContinuousProfilesSampleRate(options.getContinuousProfilesSampleRate()); } if (options.getDebug() != null) { setDebug(options.getDebug()); diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 107d9375a4..92343bdeb4 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2177,7 +2177,7 @@ class ScopesTest { val logger = mock() val scopes = generateScopes { it.setContinuousProfiler(profiler) - it.continuousProfilesSampleRate = 0.1 + it.experimental.continuousProfilesSampleRate = 0.1 it.setLogger(logger) it.isDebug = true } diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 03146f8180..4cea67c4ed 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -239,7 +239,7 @@ class SentryOptionsTest { @Test fun `when continuousProfilesSampleRate is set to a 0, isProfilingEnabled is false and isContinuousProfilingEnabled is false`() { val options = SentryOptions().apply { - this.continuousProfilesSampleRate = 0.0 + this.experimental.continuousProfilesSampleRate = 0.0 } assertFalse(options.isProfilingEnabled) assertFalse(options.isContinuousProfilingEnabled) @@ -266,19 +266,19 @@ class SentryOptionsTest { @Test fun `when setContinuousProfilesSampleRate is set to exactly 0, value is set`() { val options = SentryOptions().apply { - this.continuousProfilesSampleRate = 0.0 + this.experimental.continuousProfilesSampleRate = 0.0 } assertEquals(0.0, options.continuousProfilesSampleRate) } @Test fun `when setContinuousProfilesSampleRate is set to higher than 1_0, setter throws`() { - assertFailsWith { SentryOptions().continuousProfilesSampleRate = 1.0000000000001 } + assertFailsWith { SentryOptions().experimental.continuousProfilesSampleRate = 1.0000000000001 } } @Test fun `when setContinuousProfilesSampleRate is set to lower than 0, setter throws`() { - assertFailsWith { SentryOptions().continuousProfilesSampleRate = -0.0000000000001 } + assertFailsWith { SentryOptions().experimental.continuousProfilesSampleRate = -0.0000000000001 } } @Test @@ -607,7 +607,7 @@ class SentryOptionsTest { fun `when profiling is disabled, isEnableAppStartProfiling is always false`() { val options = SentryOptions() options.isEnableAppStartProfiling = true - options.continuousProfilesSampleRate = 0.0 + options.experimental.continuousProfilesSampleRate = 0.0 assertFalse(options.isEnableAppStartProfiling) } @@ -615,7 +615,7 @@ class SentryOptionsTest { fun `when setEnableAppStartProfiling is called and continuous profiling is enabled, isEnableAppStartProfiling is true`() { val options = SentryOptions() options.isEnableAppStartProfiling = true - options.continuousProfilesSampleRate = 1.0 + options.experimental.continuousProfilesSampleRate = 1.0 assertTrue(options.isEnableAppStartProfiling) } diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index ae0243618a..36dda68474 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -407,7 +407,7 @@ class SentryTest { var sentryOptions: SentryOptions? = null Sentry.init { it.dsn = dsn - it.continuousProfilesSampleRate = 1.0 + it.experimental.continuousProfilesSampleRate = 1.0 it.cacheDirPath = tempPath sentryOptions = it } @@ -422,7 +422,7 @@ class SentryTest { var sentryOptions: SentryOptions? = null Sentry.init { it.dsn = dsn - it.continuousProfilesSampleRate = 0.0 + it.experimental.continuousProfilesSampleRate = 0.0 it.cacheDirPath = tempPath sentryOptions = it } @@ -1336,7 +1336,7 @@ class SentryTest { Sentry.init { it.dsn = dsn it.setContinuousProfiler(profiler) - it.continuousProfilesSampleRate = 0.1 + it.experimental.continuousProfilesSampleRate = 0.1 } // We cannot set sample rate to 0, as it would not start the profiler. So we set the seed to have consistent results SentryRandom.current().setSeed(0) diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index 4523a6ecd1..2fd047c9ef 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -35,7 +35,7 @@ class TracesSamplerTest { options.profilesSampleRate = profilesSampleRate } if (continuousProfilesSampleRate != null) { - options.continuousProfilesSampleRate = continuousProfilesSampleRate + options.experimental.continuousProfilesSampleRate = continuousProfilesSampleRate } if (tracesSamplerCallback != null) { options.tracesSampler = tracesSamplerCallback From c9fbfbb99866be81ea97f006f1cd998681018cc3 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 11 Feb 2025 16:31:19 +0100 Subject: [PATCH 5/6] increased continuous profiling chunk duration to 1 minute --- .../java/io/sentry/android/core/AndroidContinuousProfiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 574adf8cef..ac43ac53c3 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 @@ -36,7 +36,7 @@ @ApiStatus.Internal public class AndroidContinuousProfiler implements IContinuousProfiler, RateLimiter.IRateLimitObserver { - private static final long MAX_CHUNK_DURATION_MILLIS = 10000; + private static final long MAX_CHUNK_DURATION_MILLIS = 60000; private final @NotNull ILogger logger; private final @Nullable String profilingTracesDirPath; From ceeac6d511b0d5aab15946339a3b3ef12780996f Mon Sep 17 00:00:00 2001 From: Stefano Date: Fri, 14 Feb 2025 10:36:46 +0100 Subject: [PATCH 6/6] Update CHANGELOG.md Co-authored-by: Roman Zavarnitsyn --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d30f3de0..721f319790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Add Continuous Profiling Support ([#3710](https://github.com/getsentry/sentry-java/pull/3710)) To enable Continuous Profiling use the `Sentry.startProfiler` and `Sentry.stopProfiler` experimental APIs. Sampling rate can be set through `options.continuousProfilesSampleRate` (defaults to 1.0). - Note: Both `options.profilesSampler` and `options.profilesSampleRate` must not be set to enable Continuous Profiling. + Note: Both `options.profilesSampler` and `options.profilesSampleRate` must **not** be set to enable Continuous Profiling. ```java import io.sentry.android.core.SentryAndroid;