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

[QA] Lazily load SentryOptions members #3749

Merged
merged 13 commits into from
Oct 8, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified
- Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669))
- Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736))
- Lazily initialize heavy `SentryOptions` members to avoid ANRs on app start ([#3749](https://github.com/getsentry/sentry-java/pull/3749))

*Breaking changes*:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.graphics.Rect
import android.graphics.RectF
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.util.Log
import android.view.PixelCopy
import android.view.View
import android.view.ViewGroup
Expand Down Expand Up @@ -101,6 +102,7 @@ internal class ScreenshotRecorder(
Bitmap.Config.ARGB_8888
)

val timeStart = System.nanoTime()
// postAtFrontOfQueue to ensure the view hierarchy and bitmap are ase close in-sync as possible
mainLooperHandler.post {
try {
Expand All @@ -123,6 +125,8 @@ internal class ScreenshotRecorder(

val viewHierarchy = ViewHierarchyNode.fromView(root, null, 0, options)
root.traverse(viewHierarchy)
val timeEnd = System.nanoTime()
Log.e("TIME", String.format("%.2f", ((timeEnd - timeStart) / 1_000_000.0)))

recorder.submitSafely(options, "screenshot_recorder.redact") {
val canvas = Canvas(bitmap)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,5 @@

<meta-data android:name="io.sentry.session-replay.session-sample-rate" android:value="1.0" />
<meta-data android:name="io.sentry.session-replay.redact-all-text" android:value="false" />

</application>
</manifest>
5 changes: 3 additions & 2 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public abstract interface class io/sentry/EventProcessor {
}

public final class io/sentry/ExperimentalOptions {
public fun <init> ()V
public fun <init> (Z)V
public fun getSessionReplay ()Lio/sentry/SentryReplayOptions;
public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V
}
Expand Down Expand Up @@ -2712,8 +2712,8 @@ public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sent
public final class io/sentry/SentryReplayOptions {
public static final field IMAGE_VIEW_CLASS_NAME Ljava/lang/String;
public static final field TEXT_VIEW_CLASS_NAME Ljava/lang/String;
public fun <init> ()V
public fun <init> (Ljava/lang/Double;Ljava/lang/Double;)V
public fun <init> (Z)V
public fun addIgnoreViewClass (Ljava/lang/String;)V
public fun addRedactViewClass (Ljava/lang/String;)V
public fun getErrorReplayDuration ()J
Expand Down Expand Up @@ -5698,6 +5698,7 @@ public final class io/sentry/util/JsonSerializationUtils {
public final class io/sentry/util/LazyEvaluator {
public fun <init> (Lio/sentry/util/LazyEvaluator$Evaluator;)V
public fun getValue ()Ljava/lang/Object;
public fun setValue (Ljava/lang/Object;)V
}

public abstract interface class io/sentry/util/LazyEvaluator$Evaluator {
Expand Down
6 changes: 5 additions & 1 deletion sentry/src/main/java/io/sentry/ExperimentalOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
* <p>Beware that experimental options can change at any time.
*/
public final class ExperimentalOptions {
private @NotNull SentryReplayOptions sessionReplay = new SentryReplayOptions();
private @NotNull SentryReplayOptions sessionReplay;

public ExperimentalOptions(final boolean empty) {
this.sessionReplay = new SentryReplayOptions(empty);
}

@NotNull
public SentryReplayOptions getSessionReplay() {
Expand Down
32 changes: 18 additions & 14 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
import io.sentry.transport.ITransportGate;
import io.sentry.transport.NoOpEnvelopeCache;
import io.sentry.transport.NoOpTransportGate;
import io.sentry.util.LazyEvaluator;
import io.sentry.util.Platform;
import io.sentry.util.SampleRateUtils;
import io.sentry.util.StringUtils;
import io.sentry.util.thread.IMainThreadChecker;
import io.sentry.util.thread.NoOpMainThreadChecker;
import java.io.File;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -118,11 +118,13 @@ public class SentryOptions {
/** minimum LogLevel to be used if debug is enabled */
private @NotNull SentryLevel diagnosticLevel = DEFAULT_DIAGNOSTIC_LEVEL;

/** Envelope reader interface */
private @NotNull IEnvelopeReader envelopeReader = new EnvelopeReader(new JsonSerializer(this));

/** Serializer interface to serialize/deserialize json events */
private @NotNull ISerializer serializer = new JsonSerializer(this);
private final @NotNull LazyEvaluator<ISerializer> serializer =
new LazyEvaluator<>(() -> new JsonSerializer(this));

/** Envelope reader interface */
private final @NotNull LazyEvaluator<IEnvelopeReader> envelopeReader =
new LazyEvaluator<>(() -> new EnvelopeReader(serializer.getValue()));

/** Max depth when serializing object graphs with reflection. * */
private int maxDepth = 100;
Expand Down Expand Up @@ -416,7 +418,8 @@ public class SentryOptions {

/** Date provider to retrieve the current date from. */
@ApiStatus.Internal
private @NotNull SentryDateProvider dateProvider = new SentryAutoDateProvider();
private final @NotNull LazyEvaluator<SentryDateProvider> dateProvider =
new LazyEvaluator<>(() -> new SentryAutoDateProvider());

private final @NotNull List<IPerformanceCollector> performanceCollectors = new ArrayList<>();

Expand Down Expand Up @@ -479,7 +482,7 @@ public class SentryOptions {

@ApiStatus.Experimental private @Nullable Cron cron = null;

private final @NotNull ExperimentalOptions experimental = new ExperimentalOptions();
private final @NotNull ExperimentalOptions experimental;

private @NotNull ReplayController replayController = NoOpReplayController.getInstance();

Expand Down Expand Up @@ -605,7 +608,7 @@ public void setDiagnosticLevel(@Nullable final SentryLevel diagnosticLevel) {
* @return the serializer
*/
public @NotNull ISerializer getSerializer() {
return serializer;
return serializer.getValue();
}

/**
Expand All @@ -614,7 +617,7 @@ public void setDiagnosticLevel(@Nullable final SentryLevel diagnosticLevel) {
* @param serializer the serializer
*/
public void setSerializer(@Nullable ISerializer serializer) {
this.serializer = serializer != null ? serializer : NoOpSerializer.getInstance();
this.serializer.setValue(serializer != null ? serializer : NoOpSerializer.getInstance());
}

/**
Expand All @@ -636,12 +639,12 @@ public void setMaxDepth(int maxDepth) {
}

public @NotNull IEnvelopeReader getEnvelopeReader() {
return envelopeReader;
return envelopeReader.getValue();
}

public void setEnvelopeReader(final @Nullable IEnvelopeReader envelopeReader) {
this.envelopeReader =
envelopeReader != null ? envelopeReader : NoOpEnvelopeReader.getInstance();
this.envelopeReader.setValue(
envelopeReader != null ? envelopeReader : NoOpEnvelopeReader.getInstance());
}

/**
Expand Down Expand Up @@ -2212,7 +2215,7 @@ public void setIgnoredCheckIns(final @Nullable List<String> ignoredCheckIns) {
/** Returns the current {@link SentryDateProvider} that is used to retrieve the current date. */
@ApiStatus.Internal
public @NotNull SentryDateProvider getDateProvider() {
return dateProvider;
return dateProvider.getValue();
}

/**
Expand All @@ -2223,7 +2226,7 @@ public void setIgnoredCheckIns(final @Nullable List<String> ignoredCheckIns) {
*/
@ApiStatus.Internal
public void setDateProvider(final @NotNull SentryDateProvider dateProvider) {
this.dateProvider = dateProvider;
this.dateProvider.setValue(dateProvider);
}

/**
Expand Down Expand Up @@ -2540,6 +2543,7 @@ public SentryOptions() {
* @param empty if options should be empty.
*/
private SentryOptions(final boolean empty) {
experimental = new ExperimentalOptions(empty);
if (!empty) {
// SentryExecutorService should be initialized before any
// SendCachedEventFireAndForgetIntegration
Expand Down
10 changes: 6 additions & 4 deletions sentry/src/main/java/io/sentry/SentryReplayOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,16 @@ public enum SentryReplayQuality {
/** The maximum duration of a full session replay, defaults to 1h. */
private long sessionDuration = 60 * 60 * 1000L;

public SentryReplayOptions() {
setRedactAllText(true);
setRedactAllImages(true);
public SentryReplayOptions(final boolean empty) {
if (!empty) {
setRedactAllText(true);
setRedactAllImages(true);
}
}

public SentryReplayOptions(
final @Nullable Double sessionSampleRate, final @Nullable Double onErrorSampleRate) {
this();
this(false);
this.sessionSampleRate = sessionSampleRate;
this.onErrorSampleRate = onErrorSampleRate;
}
Expand Down
15 changes: 8 additions & 7 deletions sentry/src/main/java/io/sentry/cache/CacheStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.sentry.SentryOptions;
import io.sentry.Session;
import io.sentry.clientreport.DiscardReason;
import io.sentry.util.LazyEvaluator;
import io.sentry.util.Objects;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
Expand All @@ -36,8 +37,9 @@ abstract class CacheStrategy {
@SuppressWarnings("CharsetObjectCanBeUsed")
protected static final Charset UTF_8 = Charset.forName("UTF-8");

protected final @NotNull SentryOptions options;
protected final @NotNull ISerializer serializer;
protected @NotNull SentryOptions options;
protected final @NotNull LazyEvaluator<ISerializer> serializer =
new LazyEvaluator<>(() -> options.getSerializer());
protected final @NotNull File directory;
private final int maxSize;

Expand All @@ -48,7 +50,6 @@ abstract class CacheStrategy {
Objects.requireNonNull(directoryPath, "Directory is required.");
this.options = Objects.requireNonNull(options, "SentryOptions is required.");

this.serializer = options.getSerializer();
this.directory = new File(directoryPath);

this.maxSize = maxSize;
Expand Down Expand Up @@ -177,7 +178,7 @@ private void moveInitFlagIfNecessary(
&& currentSession.getSessionId().equals(session.getSessionId())) {
session.setInitAsTrue();
try {
newSessionItem = SentryEnvelopeItem.fromSession(serializer, session);
newSessionItem = SentryEnvelopeItem.fromSession(serializer.getValue(), session);
// remove item from envelope items so we can replace with the new one that has the
// init flag true
itemsIterator.remove();
Expand Down Expand Up @@ -216,7 +217,7 @@ private void moveInitFlagIfNecessary(

private @Nullable SentryEnvelope readEnvelope(final @NotNull File file) {
try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return serializer.deserializeEnvelope(inputStream);
return serializer.getValue().deserializeEnvelope(inputStream);
} catch (IOException e) {
options.getLogger().log(ERROR, "Failed to deserialize the envelope.", e);
}
Expand Down Expand Up @@ -258,7 +259,7 @@ private boolean isSessionType(final @Nullable SentryEnvelopeItem item) {
try (final Reader reader =
new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) {
return serializer.deserialize(reader, Session.class);
return serializer.getValue().deserialize(reader, Session.class);
} catch (Throwable e) {
options.getLogger().log(ERROR, "Failed to deserialize the session.", e);
}
Expand All @@ -268,7 +269,7 @@ private boolean isSessionType(final @Nullable SentryEnvelopeItem item) {
private void saveNewEnvelope(
final @NotNull SentryEnvelope envelope, final @NotNull File file, final long timestamp) {
try (final OutputStream outputStream = new FileOutputStream(file)) {
serializer.serialize(envelope, outputStream);
serializer.getValue().serialize(envelope, outputStream);
// we need to set the same timestamp so the sorting from oldest to newest wont break.
file.setLastModified(timestamp);
} catch (Throwable e) {
Expand Down
12 changes: 6 additions & 6 deletions sentry/src/main/java/io/sentry/cache/EnvelopeCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void store(final @NotNull SentryEnvelope envelope, final @NotNull Hint hi
try (final Reader reader =
new BufferedReader(
new InputStreamReader(new FileInputStream(currentSessionFile), UTF_8))) {
final Session session = serializer.deserialize(reader, Session.class);
final Session session = serializer.getValue().deserialize(reader, Session.class);
if (session != null) {
writeSessionToDisk(previousSessionFile, session);
}
Expand Down Expand Up @@ -204,7 +204,7 @@ private void tryEndPreviousSession(final @NotNull Hint hint) {
try (final Reader reader =
new BufferedReader(
new InputStreamReader(new FileInputStream(previousSessionFile), UTF_8))) {
final Session session = serializer.deserialize(reader, Session.class);
final Session session = serializer.getValue().deserialize(reader, Session.class);
if (session != null) {
final AbnormalExit abnormalHint = (AbnormalExit) sdkHint;
final @Nullable Long abnormalExitTimestamp = abnormalHint.timestamp();
Expand Down Expand Up @@ -263,7 +263,7 @@ private void updateCurrentSession(
try (final Reader reader =
new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) {
final Session session = serializer.deserialize(reader, Session.class);
final Session session = serializer.getValue().deserialize(reader, Session.class);
if (session == null) {
options
.getLogger()
Expand Down Expand Up @@ -304,7 +304,7 @@ private void writeEnvelopeToDisk(
}

try (final OutputStream outputStream = new FileOutputStream(file)) {
serializer.serialize(envelope, outputStream);
serializer.getValue().serialize(envelope, outputStream);
} catch (Throwable e) {
options
.getLogger()
Expand All @@ -324,7 +324,7 @@ private void writeSessionToDisk(final @NotNull File file, final @NotNull Session

try (final OutputStream outputStream = new FileOutputStream(file);
final Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) {
serializer.serialize(session, writer);
serializer.getValue().serialize(session, writer);
} catch (Throwable e) {
options
.getLogger()
Expand Down Expand Up @@ -388,7 +388,7 @@ public void discard(final @NotNull SentryEnvelope envelope) {
for (final File file : allCachedEnvelopes) {
try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) {

ret.add(serializer.deserializeEnvelope(is));
ret.add(serializer.getValue().deserializeEnvelope(is));
} catch (FileNotFoundException e) {
options
.getLogger()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.sentry.clientreport;

import io.sentry.DataCategory;
import io.sentry.util.LazyEvaluator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.jetbrains.annotations.ApiStatus;
Expand All @@ -14,25 +16,28 @@
@ApiStatus.Internal
final class AtomicClientReportStorage implements IClientReportStorage {

private final @NotNull Map<ClientReportKey, AtomicLong> lostEventCounts;
private final @NotNull LazyEvaluator<Map<ClientReportKey, AtomicLong>> lostEventCounts =
new LazyEvaluator<>(
romtsn marked this conversation as resolved.
Show resolved Hide resolved
romtsn marked this conversation as resolved.
Show resolved Hide resolved
() -> {
final Map<ClientReportKey, AtomicLong> modifyableEventCountsForInit =
new ConcurrentHashMap<>();

public AtomicClientReportStorage() {
final Map<ClientReportKey, AtomicLong> modifyableEventCountsForInit = new ConcurrentHashMap<>();
for (final DiscardReason discardReason : DiscardReason.values()) {
romtsn marked this conversation as resolved.
Show resolved Hide resolved
for (final DataCategory category : DataCategory.values()) {
modifyableEventCountsForInit.put(
new ClientReportKey(discardReason.getReason(), category.getCategory()),
new AtomicLong(0));
}
}

for (final DiscardReason discardReason : DiscardReason.values()) {
for (final DataCategory category : DataCategory.values()) {
modifyableEventCountsForInit.put(
new ClientReportKey(discardReason.getReason(), category.getCategory()),
new AtomicLong(0));
}
}
return Collections.unmodifiableMap(modifyableEventCountsForInit);
});

lostEventCounts = Collections.unmodifiableMap(modifyableEventCountsForInit);
}
public AtomicClientReportStorage() {}

@Override
public void addCount(ClientReportKey key, Long count) {
final @Nullable AtomicLong quantity = lostEventCounts.get(key);
final @Nullable AtomicLong quantity = lostEventCounts.getValue().get(key);

if (quantity != null) {
quantity.addAndGet(count);
Expand All @@ -43,7 +48,8 @@ public void addCount(ClientReportKey key, Long count) {
public List<DiscardedEvent> resetCountsAndGet() {
final List<DiscardedEvent> discardedEvents = new ArrayList<>();

for (final Map.Entry<ClientReportKey, AtomicLong> entry : lostEventCounts.entrySet()) {
Set<Map.Entry<ClientReportKey, AtomicLong>> entrySet = lostEventCounts.getValue().entrySet();
for (final Map.Entry<ClientReportKey, AtomicLong> entry : entrySet) {
final Long quantity = entry.getValue().getAndSet(0);
if (quantity > 0) {
discardedEvents.add(
Expand Down
Loading
Loading