Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap up continuous profiling #4069

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# 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.

```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.experimental.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,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;
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
9 changes: 6 additions & 3 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,9 @@ public abstract interface class io/sentry/EventProcessor {

public final class io/sentry/ExperimentalOptions {
public fun <init> (Z)V
public fun getContinuousProfilesSampleRate ()D
public fun getSessionReplay ()Lio/sentry/SentryReplayOptions;
public fun setContinuousProfilesSampleRate (D)V
public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V
}

Expand Down Expand Up @@ -1846,7 +1848,7 @@ public final class io/sentry/PerformanceCollectionData {

public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
public fun <init> ()V
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/io/File;Ljava/util/Map;Lio/sentry/SentryOptions;)V
public fun <init> (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;
Expand All @@ -1857,6 +1859,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;
Expand All @@ -1868,7 +1871,7 @@ public final class io/sentry/ProfileChunk : io/sentry/JsonSerializable, io/sentr
}

public final class io/sentry/ProfileChunk$Builder {
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Ljava/util/Map;Ljava/io/File;)V
public fun <init> (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;
}

Expand All @@ -1888,6 +1891,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 <init> ()V
}
Expand Down Expand Up @@ -3066,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
Expand Down
26 changes: 26 additions & 0 deletions sentry/src/main/java/io/sentry/ExperimentalOptions.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.sentry;

import io.sentry.util.SampleRateUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

/**
Expand All @@ -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);
}
Expand All @@ -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;
}
}
23 changes: 21 additions & 2 deletions sentry/src/main/java/io/sentry/ProfileChunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -40,6 +41,7 @@ public ProfileChunk() {
SentryId.EMPTY_ID,
new File("dummy"),
new HashMap<>(),
0.0,
SentryOptions.empty());
}

Expand All @@ -48,6 +50,7 @@ public ProfileChunk(
final @NotNull SentryId chunkId,
final @NotNull File traceFile,
final @NotNull Map<String, ProfileMeasurement> measurements,
final @NotNull Double timestamp,
final @NotNull SentryOptions options) {
this.profilerId = profilerId;
this.chunkId = chunkId;
Expand All @@ -59,6 +62,7 @@ public ProfileChunk(
this.environment = options.getEnvironment();
this.platform = "android";
this.version = "2";
this.timestamp = timestamp;
}

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

public double getTimestamp() {
return timestamp;
}

public @NotNull String getVersion() {
return version;
}
Expand Down Expand Up @@ -152,20 +160,23 @@ public static final class Builder {
private final @NotNull SentryId chunkId;
private final @NotNull Map<String, ProfileMeasurement> measurements;
private final @NotNull File traceFile;
private final double timestamp;

public Builder(
final @NotNull SentryId profilerId,
final @NotNull SentryId chunkId,
final @NotNull Map<String, ProfileMeasurement> 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);
}
}

Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -301,6 +314,12 @@ public static final class Deserializer implements JsonDeserializer<ProfileChunk>
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<>();
Expand Down
4 changes: 3 additions & 1 deletion sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
28 changes: 6 additions & 22 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1713,6 +1704,7 @@ public void setTransactionProfiler(final @Nullable ITransactionProfiler transact
*
* @return the continuous profiler.
*/
@ApiStatus.Experimental
public @NotNull IContinuousProfiler getContinuousProfiler() {
return continuousProfiler;
}
Expand All @@ -1722,6 +1714,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()
Expand Down Expand Up @@ -1749,7 +1742,7 @@ public boolean isProfilingEnabled() {
public boolean isContinuousProfilingEnabled() {
return profilesSampleRate == null
&& profilesSampler == null
&& continuousProfilesSampleRate > 0;
&& experimental.getContinuousProfilesSampleRate() > 0;
}

/**
Expand Down Expand Up @@ -1803,18 +1796,9 @@ public void setProfilesSampleRate(final @Nullable Double profilesSampleRate) {
*
* @return the sample rate
*/
@ApiStatus.Experimental
public double getContinuousProfilesSampleRate() {
return continuousProfilesSampleRate;
}

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();
}

/**
Expand Down Expand Up @@ -2744,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());
Expand Down
Loading
Loading