Skip to content

Commit

Permalink
[QA] Lazily load SentryOptions members (#3749)
Browse files Browse the repository at this point in the history
* Make AtomicClientReportStorage constructor lazy

* Lazily initialize things we dont need

* Empty experimental options

* Format code

* Make LazyEvaluator thread-safe

* Fix tests

* Fix .api

* Use LazyEvaluator for heavy SentryOptions

* Changelog

* revert

* tests

---------

Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
Co-authored-by: Markus Hintersteiner <markus.hintersteiner@sentry.io>
  • Loading branch information
3 people authored Oct 8, 2024
1 parent 955c6ee commit 503f916
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 63 deletions.
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<>(
() -> {
final Map<ClientReportKey, AtomicLong> modifyableEventCountsForInit =
new ConcurrentHashMap<>();

public AtomicClientReportStorage() {
final Map<ClientReportKey, AtomicLong> modifyableEventCountsForInit = new ConcurrentHashMap<>();
for (final DiscardReason discardReason : DiscardReason.values()) {
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

0 comments on commit 503f916

Please sign in to comment.