Skip to content

Commit

Permalink
Merge a44b5e3 into 4163588
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanosiano authored Dec 29, 2023
2 parents 4163588 + a44b5e3 commit 47df090
Show file tree
Hide file tree
Showing 11 changed files with 513 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Handle `monitor`/`check_in` in client reports and rate limiter ([#3096](https://github.com/getsentry/sentry-java/pull/3096))
- Startup profiling 1 - Decouple Profiler from Transaction ([#3101](https://github.com/getsentry/sentry-java/pull/3101))
- Startup profiling 2 - Add options and sampling logic ([#3121](https://github.com/getsentry/sentry-java/pull/3121))

### Fixes

Expand Down
4 changes: 4 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,7 @@ public class io/sentry/SentryOptions {
public fun isEnableExternalConfiguration ()Z
public fun isEnablePrettySerializationOutput ()Z
public fun isEnableShutdownHook ()Z
public fun isEnableStartupProfiling ()Z
public fun isEnableTimeToFullDisplayTracing ()Z
public fun isEnableUncaughtExceptionHandler ()Z
public fun isEnableUserInteractionBreadcrumbs ()Z
Expand Down Expand Up @@ -2211,6 +2212,7 @@ public class io/sentry/SentryOptions {
public fun setEnableExternalConfiguration (Z)V
public fun setEnablePrettySerializationOutput (Z)V
public fun setEnableShutdownHook (Z)V
public fun setEnableStartupProfiling (Z)V
public fun setEnableTimeToFullDisplayTracing (Z)V
public fun setEnableTracing (Ljava/lang/Boolean;)V
public fun setEnableUncaughtExceptionHandler (Z)V
Expand Down Expand Up @@ -2716,6 +2718,8 @@ public final class io/sentry/TransactionContext : io/sentry/SpanContext {
public fun getParentSampled ()Ljava/lang/Boolean;
public fun getParentSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
public fun isForNextStartup ()Z
public fun setForNextStartup (Z)V
public fun setInstrumenter (Lio/sentry/Instrumenter;)V
public fun setName (Ljava/lang/String;)V
public fun setParentSampled (Ljava/lang/Boolean;)V
Expand Down
2 changes: 2 additions & 0 deletions sentry/src/main/java/io/sentry/JsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ public JsonSerializer(@NotNull SentryOptions options) {
deserializersByClass.put(SentrySpan.class, new SentrySpan.Deserializer());
deserializersByClass.put(SentryStackFrame.class, new SentryStackFrame.Deserializer());
deserializersByClass.put(SentryStackTrace.class, new SentryStackTrace.Deserializer());
deserializersByClass.put(
SentryStartupProfilingOptions.class, new SentryStartupProfilingOptions.Deserializer());
deserializersByClass.put(SentryThread.class, new SentryThread.Deserializer());
deserializersByClass.put(SentryTransaction.class, new SentryTransaction.Deserializer());
deserializersByClass.put(Session.class, new Session.Deserializer());
Expand Down
62 changes: 59 additions & 3 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.sentry.cache.EnvelopeCache;
import io.sentry.cache.IEnvelopeCache;
import io.sentry.config.PropertiesProviderFactory;
import io.sentry.instrumentation.file.SentryFileWriter;
import io.sentry.internal.debugmeta.NoOpDebugMetaLoader;
import io.sentry.internal.debugmeta.ResourcesDebugMetaLoader;
import io.sentry.internal.modules.CompositeModulesLoader;
Expand All @@ -21,6 +22,8 @@
import io.sentry.util.thread.MainThreadChecker;
import io.sentry.util.thread.NoOpMainThreadChecker;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
Expand All @@ -47,6 +50,9 @@ private Sentry() {}
/** whether to use a single (global) Hub as opposed to one per thread. */
private static volatile boolean globalHubMode = GLOBAL_HUB_DEFAULT_MODE;

private static final @NotNull String STARTUP_PROFILING_CONFIG_FILE_NAME =
"startup_profiling_config";

/**
* Returns the current (threads) hub, if none, clones the mainHub and returns it.
*
Expand Down Expand Up @@ -225,9 +231,7 @@ private static synchronized void init(

// If the executorService passed in the init is the same that was previously closed, we have to
// set a new one
final ISentryExecutorService sentryExecutorService = options.getExecutorService();
// If the passed executor service was previously called we set a new one
if (sentryExecutorService.isClosed()) {
if (options.getExecutorService().isClosed()) {
options.setExecutorService(new SentryExecutorService());
}

Expand All @@ -242,6 +246,58 @@ private static synchronized void init(
notifyOptionsObservers(options);

finalizePreviousSession(options, HubAdapter.getInstance());

handleStartupProfilingConfig(options, options.getExecutorService());
}

@SuppressWarnings("FutureReturnValueIgnored")
private static void handleStartupProfilingConfig(
final @NotNull SentryOptions options,
final @NotNull ISentryExecutorService sentryExecutorService) {
sentryExecutorService.submit(
() -> {
final String cacheDirPath = options.getCacheDirPathWithoutDsn();
if (cacheDirPath != null) {
final @NotNull File startupProfilingConfigFile =
new File(cacheDirPath, STARTUP_PROFILING_CONFIG_FILE_NAME);
// We always delete the config file for startup profiling
FileUtils.deleteRecursively(startupProfilingConfigFile);
if (!options.isEnableStartupProfiling()) {
return;
}
if (!options.isTracingEnabled()) {
options
.getLogger()
.log(
SentryLevel.INFO,
"Tracing is disabled and startup profiling will not start.");
return;
}
try {
if (startupProfilingConfigFile.createNewFile()) {
final @NotNull TracesSamplingDecision startupSamplingDecision =
sampleStartupProfiling(options);
final @NotNull SentryStartupProfilingOptions startupProfilingOptions =
new SentryStartupProfilingOptions(options, startupSamplingDecision);
try (Writer fileWriter = new SentryFileWriter(startupProfilingConfigFile)) {
options.getSerializer().serialize(startupProfilingOptions, fileWriter);
}
}
} catch (IOException e) {
options
.getLogger()
.log(SentryLevel.ERROR, "Unable to create startup profiling config file. ", e);
}
}
});
}

private static @NotNull TracesSamplingDecision sampleStartupProfiling(
final @NotNull SentryOptions options) {
TransactionContext startupTransactionContext = new TransactionContext("ui.load", "");
startupTransactionContext.setForNextStartup(true);
SamplingContext startupSamplingContext = new SamplingContext(startupTransactionContext, null);
return new TracesSampler(options).sample(startupSamplingContext);
}

@SuppressWarnings("FutureReturnValueIgnored")
Expand Down
36 changes: 36 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ public class SentryOptions {

@ApiStatus.Experimental private boolean enableBackpressureHandling = false;

/** Whether to enable startup profiling, depending on profilesSampler or profilesSampleRate. */
private boolean enableStartupProfiling = false;

/**
* Adds an event processor
*
Expand Down Expand Up @@ -730,6 +733,20 @@ public void setBeforeBreadcrumb(@Nullable BeforeBreadcrumbCallback beforeBreadcr
return dsnHash != null ? new File(cacheDirPath, dsnHash).getAbsolutePath() : cacheDirPath;
}

/**
* Returns the cache dir path if set, without the appended dsn hash.
*
* @return the cache dir path, without the appended dsn hash, or null if not set.
*/
@Nullable
String getCacheDirPathWithoutDsn() {
if (cacheDirPath == null || cacheDirPath.isEmpty()) {
return null;
}

return cacheDirPath;
}

/**
* Returns the outbox path if cacheDirPath is set
*
Expand Down Expand Up @@ -2122,6 +2139,25 @@ public void setEnablePrettySerializationOutput(boolean enablePrettySerialization
this.enablePrettySerializationOutput = enablePrettySerializationOutput;
}

/**
* Whether to enable startup profiling, depending on profilesSampler or profilesSampleRate.
* Depends on {@link SentryOptions#isProfilingEnabled()}
*
* @return true if startup profiling should be started.
*/
public boolean isEnableStartupProfiling() {
return isProfilingEnabled() && enableStartupProfiling;
}

/**
* Whether to enable startup profiling, depending on profilesSampler or profilesSampleRate.
*
* @param enableStartupProfiling true if startup profiling should be started.
*/
public void setEnableStartupProfiling(boolean enableStartupProfiling) {
this.enableStartupProfiling = enableStartupProfiling;
}

/**
* Whether to send modules containing information about versions.
*
Expand Down
146 changes: 146 additions & 0 deletions sentry/src/main/java/io/sentry/SentryStartupProfilingOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.sentry;

import io.sentry.vendor.gson.stream.JsonToken;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class SentryStartupProfilingOptions implements JsonUnknown, JsonSerializable {

boolean profileSampled;
@Nullable Double profileSampleRate;
boolean traceSampled;
@Nullable Double traceSampleRate;
@Nullable String profilingTracesDirPath;
boolean isProfilingEnabled;

private @Nullable Map<String, Object> unknown;

SentryStartupProfilingOptions() {
traceSampled = false;
traceSampleRate = null;
profileSampled = false;
profileSampleRate = null;
profilingTracesDirPath = null;
isProfilingEnabled = false;
}

SentryStartupProfilingOptions(
final @NotNull SentryOptions options,
final @NotNull TracesSamplingDecision samplingDecision) {
traceSampled = samplingDecision.getSampled();
traceSampleRate = samplingDecision.getSampleRate();
profileSampled = samplingDecision.getProfileSampled();
profileSampleRate = samplingDecision.getProfileSampleRate();
profilingTracesDirPath = options.getProfilingTracesDirPath();
isProfilingEnabled = options.isProfilingEnabled();
}

// JsonSerializable

public static final class JsonKeys {
public static final String PROFILE_SAMPLED = "profile_sampled";
public static final String PROFILE_SAMPLE_RATE = "profile_sample_rate";
public static final String TRACE_SAMPLED = "trace_sampled";
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";
}

@Override
public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger)
throws IOException {
writer.beginObject();
writer.name(JsonKeys.PROFILE_SAMPLED).value(logger, profileSampled);
writer.name(JsonKeys.PROFILE_SAMPLE_RATE).value(logger, profileSampleRate);
writer.name(JsonKeys.TRACE_SAMPLED).value(logger, traceSampled);
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);

if (unknown != null) {
for (String key : unknown.keySet()) {
Object value = unknown.get(key);
writer.name(key);
writer.value(logger, value);
}
}
writer.endObject();
}

@Nullable
@Override
public Map<String, Object> getUnknown() {
return unknown;
}

@Override
public void setUnknown(@Nullable Map<String, Object> unknown) {
this.unknown = unknown;
}

public static final class Deserializer
implements JsonDeserializer<SentryStartupProfilingOptions> {

@Override
public @NotNull SentryStartupProfilingOptions deserialize(
@NotNull JsonObjectReader reader, @NotNull ILogger logger) throws Exception {
reader.beginObject();
SentryStartupProfilingOptions options = new SentryStartupProfilingOptions();
Map<String, Object> unknown = null;

while (reader.peek() == JsonToken.NAME) {
final String nextName = reader.nextName();
switch (nextName) {
case JsonKeys.PROFILE_SAMPLED:
Boolean profileSampled = reader.nextBooleanOrNull();
if (profileSampled != null) {
options.profileSampled = profileSampled;
}
break;
case JsonKeys.PROFILE_SAMPLE_RATE:
Double profileSampleRate = reader.nextDoubleOrNull();
if (profileSampleRate != null) {
options.profileSampleRate = profileSampleRate;
}
break;
case JsonKeys.TRACE_SAMPLED:
Boolean traceSampled = reader.nextBooleanOrNull();
if (traceSampled != null) {
options.traceSampled = traceSampled;
}
break;
case JsonKeys.TRACE_SAMPLE_RATE:
Double traceSampleRate = reader.nextDoubleOrNull();
if (traceSampleRate != null) {
options.traceSampleRate = traceSampleRate;
}
break;
case JsonKeys.PROFILING_TRACES_DIR_PATH:
String profilingTracesDirPath = reader.nextStringOrNull();
if (profilingTracesDirPath != null) {
options.profilingTracesDirPath = profilingTracesDirPath;
}
break;
case JsonKeys.IS_PROFILING_ENABLED:
Boolean isProfilingEnabled = reader.nextBooleanOrNull();
if (isProfilingEnabled != null) {
options.isProfilingEnabled = isProfilingEnabled;
}
break;
default:
if (unknown == null) {
unknown = new ConcurrentHashMap<>();
}
reader.nextUnknown(logger, unknown, nextName);
break;
}
}
options.setUnknown(unknown);
reader.endObject();
return options;
}
}
}
17 changes: 17 additions & 0 deletions sentry/src/main/java/io/sentry/TransactionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class TransactionContext extends SpanContext {
private @Nullable TracesSamplingDecision parentSamplingDecision;
private @Nullable Baggage baggage;
private @NotNull Instrumenter instrumenter = Instrumenter.SENTRY;
private boolean isForNextStartup = false;

/**
* Creates {@link TransactionContext} from sentry-trace header.
Expand Down Expand Up @@ -200,4 +201,20 @@ public void setName(final @NotNull String name) {
public void setTransactionNameSource(final @NotNull TransactionNameSource transactionNameSource) {
this.transactionNameSource = transactionNameSource;
}

@ApiStatus.Internal
public void setForNextStartup(final boolean forNextStartup) {
isForNextStartup = forNextStartup;
}

/**
* Whether this {@link TransactionContext} evaluates for the next startup. If this is true, it
* gets called only once when the SDK initializes. This is set only if {@link
* SentryOptions#isEnableStartupProfiling()} is true.
*
* @return True if this {@link TransactionContext} will be used for the next startup.
*/
public boolean isForNextStartup() {
return isForNextStartup;
}
}
Loading

0 comments on commit 47df090

Please sign in to comment.