Skip to content

Commit 87bdd6d

Browse files
authored
Wrap up continuous profiling (#4069)
* Set continuousProfilesSampleRate and startProfiler() and stopProfiler() as experimental * Added chunk start timestamp to ProfileChunk * increased continuous profiling chunk duration to 1 minute
1 parent e70d583 commit 87bdd6d

File tree

15 files changed

+132
-46
lines changed

15 files changed

+132
-46
lines changed

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add Continuous Profiling Support ([#3710](https://github.com/getsentry/sentry-java/pull/3710))
8+
9+
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).
10+
Note: Both `options.profilesSampler` and `options.profilesSampleRate` must **not** be set to enable Continuous Profiling.
11+
12+
```java
13+
import io.sentry.android.core.SentryAndroid;
14+
15+
SentryAndroid.init(context) { options ->
16+
17+
// Currently under experimental options:
18+
options.getExperimental().setContinuousProfilesSampleRate(1.0);
19+
}
20+
// Start profiling
21+
Sentry.startProfiler();
22+
23+
// After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped.
24+
Sentry.stopProfiler();
25+
```
26+
```kotlin
27+
import io.sentry.android.core.SentryAndroid
28+
29+
SentryAndroid.init(context) { options ->
30+
31+
// Currently under experimental options:
32+
options.experimental.continuousProfilesSampleRate = 1.0
33+
}
34+
// Start profiling
35+
Sentry.startProfiler()
36+
37+
// After all profiling is done, stop the profiler. Profiles can last indefinitely if not stopped.
38+
Sentry.stopProfiler()
39+
```
40+
41+
To learn more visit [Sentry's Continuous Profiling](https://docs.sentry.io/product/explore/profiling/transaction-vs-continuous-profiling/#continuous-profiling-mode) documentation page.
42+
343
## 8.0.0-beta.3
444

545
### Features

sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import io.sentry.PerformanceCollectionData;
1717
import io.sentry.ProfileChunk;
1818
import io.sentry.Sentry;
19+
import io.sentry.SentryDate;
1920
import io.sentry.SentryLevel;
21+
import io.sentry.SentryNanotimeDate;
2022
import io.sentry.SentryOptions;
2123
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
2224
import io.sentry.protocol.SentryId;
@@ -34,7 +36,7 @@
3436
@ApiStatus.Internal
3537
public class AndroidContinuousProfiler
3638
implements IContinuousProfiler, RateLimiter.IRateLimitObserver {
37-
private static final long MAX_CHUNK_DURATION_MILLIS = 10000;
39+
private static final long MAX_CHUNK_DURATION_MILLIS = 60000;
3840

3941
private final @NotNull ILogger logger;
4042
private final @Nullable String profilingTracesDirPath;
@@ -52,6 +54,7 @@ public class AndroidContinuousProfiler
5254
private @NotNull SentryId profilerId = SentryId.EMPTY_ID;
5355
private @NotNull SentryId chunkId = SentryId.EMPTY_ID;
5456
private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false);
57+
private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate();
5558

5659
public AndroidContinuousProfiler(
5760
final @NotNull BuildInfoProvider buildInfoProvider,
@@ -138,8 +141,10 @@ public synchronized void start() {
138141
stop();
139142
return;
140143
}
144+
startProfileChunkTimestamp = scopes.getOptions().getDateProvider().now();
145+
} else {
146+
startProfileChunkTimestamp = new SentryNanotimeDate();
141147
}
142-
143148
final AndroidProfiler.ProfileStartData startData = profiler.start();
144149
// check if profiling started
145150
if (startData == null) {
@@ -213,7 +218,11 @@ private synchronized void stop(final boolean restartProfiler) {
213218
synchronized (payloadBuilders) {
214219
payloadBuilders.add(
215220
new ProfileChunk.Builder(
216-
profilerId, chunkId, endData.measurementsMap, endData.traceFile));
221+
profilerId,
222+
chunkId,
223+
endData.measurementsMap,
224+
endData.traceFile,
225+
startProfileChunkTimestamp));
217226
}
218227
}
219228

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ static void applyMetadata(
322322
final double continuousProfilesSampleRate =
323323
readDouble(metadata, logger, CONTINUOUS_PROFILES_SAMPLE_RATE);
324324
if (continuousProfilesSampleRate != -1) {
325-
options.setContinuousProfilesSampleRate(continuousProfilesSampleRate);
325+
options.getExperimental().setContinuousProfilesSampleRate(continuousProfilesSampleRate);
326326
}
327327
}
328328

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ class ManifestMetadataReaderTest {
818818
fun `applyMetadata does not override continuousProfilesSampleRate from options`() {
819819
// Arrange
820820
val expectedSampleRate = 0.99f
821-
fixture.options.continuousProfilesSampleRate = expectedSampleRate.toDouble()
821+
fixture.options.experimental.continuousProfilesSampleRate = expectedSampleRate.toDouble()
822822
val bundle = bundleOf(ManifestMetadataReader.CONTINUOUS_PROFILES_SAMPLE_RATE to 0.1f)
823823
val context = fixture.getContext(metaData = bundle)
824824

sentry/api/sentry.api

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,9 @@ public abstract interface class io/sentry/EventProcessor {
443443

444444
public final class io/sentry/ExperimentalOptions {
445445
public fun <init> (Z)V
446+
public fun getContinuousProfilesSampleRate ()D
446447
public fun getSessionReplay ()Lio/sentry/SentryReplayOptions;
448+
public fun setContinuousProfilesSampleRate (D)V
447449
public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V
448450
}
449451

@@ -1846,7 +1848,7 @@ public final class io/sentry/PerformanceCollectionData {
18461848

18471849
public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
18481850
public fun <init> ()V
1849-
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/io/File;Ljava/util/Map;Lio/sentry/SentryOptions;)V
1851+
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/io/File;Ljava/util/Map;Ljava/lang/Double;Lio/sentry/SentryOptions;)V
18501852
public fun equals (Ljava/lang/Object;)Z
18511853
public fun getChunkId ()Lio/sentry/protocol/SentryId;
18521854
public fun getClientSdk ()Lio/sentry/protocol/SdkVersion;
@@ -1857,6 +1859,7 @@ public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentr
18571859
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
18581860
public fun getRelease ()Ljava/lang/String;
18591861
public fun getSampledProfile ()Ljava/lang/String;
1862+
public fun getTimestamp ()D
18601863
public fun getTraceFile ()Ljava/io/File;
18611864
public fun getUnknown ()Ljava/util/Map;
18621865
public fun getVersion ()Ljava/lang/String;
@@ -1868,7 +1871,7 @@ public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentr
18681871
}
18691872

18701873
public final class io/sentry/ProfileChunk$Builder {
1871-
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/util/Map;Ljava/io/File;)V
1874+
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/util/Map;Ljava/io/File;Lio/sentry/SentryDate;)V
18721875
public fun build (Lio/sentry/SentryOptions;)Lio/sentry/ProfileChunk;
18731876
}
18741877

@@ -1888,6 +1891,7 @@ public final class io/sentry/ProfileChunk$JsonKeys {
18881891
public static final field PROFILER_ID Ljava/lang/String;
18891892
public static final field RELEASE Ljava/lang/String;
18901893
public static final field SAMPLED_PROFILE Ljava/lang/String;
1894+
public static final field TIMESTAMP Ljava/lang/String;
18911895
public static final field VERSION Ljava/lang/String;
18921896
public fun <init> ()V
18931897
}
@@ -3066,7 +3070,6 @@ public class io/sentry/SentryOptions {
30663070
public fun setConnectionStatusProvider (Lio/sentry/IConnectionStatusProvider;)V
30673071
public fun setConnectionTimeoutMillis (I)V
30683072
public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
3069-
public fun setContinuousProfilesSampleRate (D)V
30703073
public fun setCron (Lio/sentry/SentryOptions$Cron;)V
30713074
public fun setDateProvider (Lio/sentry/SentryDateProvider;)V
30723075
public fun setDebug (Z)V

sentry/src/main/java/io/sentry/ExperimentalOptions.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.sentry;
22

3+
import io.sentry.util.SampleRateUtils;
4+
import org.jetbrains.annotations.ApiStatus;
35
import org.jetbrains.annotations.NotNull;
46

57
/**
@@ -11,6 +13,15 @@
1113
public final class ExperimentalOptions {
1214
private @NotNull SentryReplayOptions sessionReplay;
1315

16+
/**
17+
* Configures the continuous profiling sample rate as a percentage of profiles to be sent in the
18+
* 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
19+
* only 10% of profiles will be sent. Profiles are picked randomly. Default is 1 (100%).
20+
* ProfilesSampleRate takes precedence over this. To enable continuous profiling, don't set
21+
* profilesSampleRate or profilesSampler, or set them to null.
22+
*/
23+
private double continuousProfilesSampleRate = 1.0;
24+
1425
public ExperimentalOptions(final boolean empty) {
1526
this.sessionReplay = new SentryReplayOptions(empty);
1627
}
@@ -23,4 +34,19 @@ public SentryReplayOptions getSessionReplay() {
2334
public void setSessionReplay(final @NotNull SentryReplayOptions sessionReplayOptions) {
2435
this.sessionReplay = sessionReplayOptions;
2536
}
37+
38+
public double getContinuousProfilesSampleRate() {
39+
return continuousProfilesSampleRate;
40+
}
41+
42+
@ApiStatus.Experimental
43+
public void setContinuousProfilesSampleRate(final double continuousProfilesSampleRate) {
44+
if (!SampleRateUtils.isValidContinuousProfilesSampleRate(continuousProfilesSampleRate)) {
45+
throw new IllegalArgumentException(
46+
"The value "
47+
+ continuousProfilesSampleRate
48+
+ " is not valid. Use values between 0.0 and 1.0.");
49+
}
50+
this.continuousProfilesSampleRate = continuousProfilesSampleRate;
51+
}
2652
}

sentry/src/main/java/io/sentry/ProfileChunk.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class ProfileChunk implements JsonUnknown, JsonSerializable {
2626
private @NotNull String release;
2727
private @Nullable String environment;
2828
private @NotNull String version;
29+
private double timestamp;
2930

3031
private final @NotNull File traceFile;
3132

@@ -40,6 +41,7 @@ public ProfileChunk() {
4041
SentryId.EMPTY_ID,
4142
new File("dummy"),
4243
new HashMap<>(),
44+
0.0,
4345
SentryOptions.empty());
4446
}
4547

@@ -48,6 +50,7 @@ public ProfileChunk(
4850
final @NotNull SentryId chunkId,
4951
final @NotNull File traceFile,
5052
final @NotNull Map<String, ProfileMeasurement> measurements,
53+
final @NotNull Double timestamp,
5154
final @NotNull SentryOptions options) {
5255
this.profilerId = profilerId;
5356
this.chunkId = chunkId;
@@ -59,6 +62,7 @@ public ProfileChunk(
5962
this.environment = options.getEnvironment();
6063
this.platform = "android";
6164
this.version = "2";
65+
this.timestamp = timestamp;
6266
}
6367

6468
public @NotNull Map<String, ProfileMeasurement> getMeasurements() {
@@ -109,6 +113,10 @@ public void setSampledProfile(final @Nullable String sampledProfile) {
109113
return traceFile;
110114
}
111115

116+
public double getTimestamp() {
117+
return timestamp;
118+
}
119+
112120
public @NotNull String getVersion() {
113121
return version;
114122
}
@@ -152,20 +160,23 @@ public static final class Builder {
152160
private final @NotNull SentryId chunkId;
153161
private final @NotNull Map<String, ProfileMeasurement> measurements;
154162
private final @NotNull File traceFile;
163+
private final double timestamp;
155164

156165
public Builder(
157166
final @NotNull SentryId profilerId,
158167
final @NotNull SentryId chunkId,
159168
final @NotNull Map<String, ProfileMeasurement> measurements,
160-
final @NotNull File traceFile) {
169+
final @NotNull File traceFile,
170+
final @NotNull SentryDate timestamp) {
161171
this.profilerId = profilerId;
162172
this.chunkId = chunkId;
163173
this.measurements = new ConcurrentHashMap<>(measurements);
164174
this.traceFile = traceFile;
175+
this.timestamp = DateUtils.nanosToSeconds(timestamp.nanoTimestamp());
165176
}
166177

167178
public ProfileChunk build(SentryOptions options) {
168-
return new ProfileChunk(profilerId, chunkId, traceFile, measurements, options);
179+
return new ProfileChunk(profilerId, chunkId, traceFile, measurements, timestamp, options);
169180
}
170181
}
171182

@@ -182,6 +193,7 @@ public static final class JsonKeys {
182193
public static final String ENVIRONMENT = "environment";
183194
public static final String VERSION = "version";
184195
public static final String SAMPLED_PROFILE = "sampled_profile";
196+
public static final String TIMESTAMP = "timestamp";
185197
}
186198

187199
@Override
@@ -208,6 +220,7 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
208220
if (sampledProfile != null) {
209221
writer.name(JsonKeys.SAMPLED_PROFILE).value(logger, sampledProfile);
210222
}
223+
writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp);
211224
if (unknown != null) {
212225
for (String key : unknown.keySet()) {
213226
Object value = unknown.get(key);
@@ -301,6 +314,12 @@ public static final class Deserializer implements JsonDeserializer<ProfileChunk>
301314
data.sampledProfile = sampledProfile;
302315
}
303316
break;
317+
case JsonKeys.TIMESTAMP:
318+
Double timestamp = reader.nextDoubleOrNull();
319+
if (timestamp != null) {
320+
data.timestamp = timestamp;
321+
}
322+
break;
304323
default:
305324
if (unknown == null) {
306325
unknown = new ConcurrentHashMap<>();

sentry/src/main/java/io/sentry/Sentry.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,11 +1051,13 @@ public static void endSession() {
10511051
}
10521052

10531053
/** Starts the continuous profiler, if enabled. */
1054+
@ApiStatus.Experimental
10541055
public static void startProfiler() {
10551056
getCurrentScopes().startProfiler();
10561057
}
10571058

1058-
/** Starts the continuous profiler, if enabled. */
1059+
/** Stops the continuous profiler, if enabled. */
1060+
@ApiStatus.Experimental
10591061
public static void stopProfiler() {
10601062
getCurrentScopes().stopProfiler();
10611063
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,6 @@ public class SentryOptions {
357357
*/
358358
private @Nullable ProfilesSamplerCallback profilesSampler;
359359

360-
/**
361-
* Configures the continuous profiling sample rate as a percentage of profiles to be sent in the
362-
* 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
363-
* only 10% of profiles will be sent. Profiles are picked randomly. Default is 1 (100%).
364-
* ProfilesSampleRate takes precedence over this. To enable continuous profiling, don't set
365-
* profilesSampleRate or profilesSampler, or set them to null.
366-
*/
367-
private double continuousProfilesSampleRate = 1.0;
368-
369360
/** Max trace file size in bytes. */
370361
private long maxTraceFileSize = 5 * 1024 * 1024;
371362

@@ -1713,6 +1704,7 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact
17131704
*
17141705
* @return the continuous profiler.
17151706
*/
1707+
@ApiStatus.Experimental
17161708
public @NotNull IContinuousProfiler getContinuousProfiler() {
17171709
return continuousProfiler;
17181710
}
@@ -1722,6 +1714,7 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact
17221714
*
17231715
* @param continuousProfiler - the continuous profiler
17241716
*/
1717+
@ApiStatus.Experimental
17251718
public void setContinuousProfiler(final @Nullable IContinuousProfiler continuousProfiler) {
17261719
// We allow to set the profiler only if it was not set before, and we don't allow to unset it.
17271720
if (this.continuousProfiler == NoOpContinuousProfiler.getInstance()
@@ -1749,7 +1742,7 @@ public boolean isProfilingEnabled() {
17491742
public boolean isContinuousProfilingEnabled() {
17501743
return profilesSampleRate == null
17511744
&& profilesSampler == null
1752-
&& continuousProfilesSampleRate > 0;
1745+
&& experimental.getContinuousProfilesSampleRate() > 0;
17531746
}
17541747

17551748
/**
@@ -1803,18 +1796,9 @@ public void setProfilesSampleRate(final @Nullable Double profilesSampleRate) {
18031796
*
18041797
* @return the sample rate
18051798
*/
1799+
@ApiStatus.Experimental
18061800
public double getContinuousProfilesSampleRate() {
1807-
return continuousProfilesSampleRate;
1808-
}
1809-
1810-
public void setContinuousProfilesSampleRate(final double continuousProfilesSampleRate) {
1811-
if (!SampleRateUtils.isValidContinuousProfilesSampleRate(continuousProfilesSampleRate)) {
1812-
throw new IllegalArgumentException(
1813-
"The value "
1814-
+ continuousProfilesSampleRate
1815-
+ " is not valid. Use values between 0.0 and 1.0.");
1816-
}
1817-
this.continuousProfilesSampleRate = continuousProfilesSampleRate;
1801+
return experimental.getContinuousProfilesSampleRate();
18181802
}
18191803

18201804
/**
@@ -2744,7 +2728,7 @@ public void merge(final @NotNull ExternalOptions options) {
27442728
setProfilesSampleRate(options.getProfilesSampleRate());
27452729
}
27462730
if (options.getContinuousProfilesSampleRate() != null) {
2747-
setContinuousProfilesSampleRate(options.getContinuousProfilesSampleRate());
2731+
experimental.setContinuousProfilesSampleRate(options.getContinuousProfilesSampleRate());
27482732
}
27492733
if (options.getDebug() != null) {
27502734
setDebug(options.getDebug());

0 commit comments

Comments
 (0)