Skip to content

Commit 69bdef7

Browse files
runningcodeclaude
andauthored
perf(android): Parse app start profiling config without JsonSerializer (#5867)
* perf(android): Parse app start profiling config without JsonSerializer (JAVA-621) SentryPerformanceProvider.launchAppStartProfiler ran in ContentProvider.onCreate — main thread, before Application.onCreate, on every cold start — and built a full JsonSerializer(SentryOptions.empty()) to read one small config file. That path only ever reads options.getLogger(), but JsonSerializer's constructor registers every known deserializer to use exactly one of them. Calling the deserializer directly cuts the parse from 221 to 33 allocations and ~7.5us to ~3.7us (Pixel 10, androidx Microbenchmark, allocationCount is deterministic). Malformed input still yields null so callers keep reporting it as a deserialization failure rather than a read error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * changelog * ref(android): Catch only expected exceptions when parsing profiling config Narrow the deserialization catch from Throwable to Exception. The vendored JSON reader signals bad input with IOException (MalformedJsonException, EOFException), IllegalStateException on token type mismatch, and NumberFormatException on an unparseable number — all Exception subclasses. Catching Throwable additionally swallowed Error, which is never a recoverable "config file is bad" signal. This also restores parity with JsonSerializer.deserialize, which catches Exception, so the replaced behaviour is matched exactly rather than widened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ref(android): Trim javadoc on deserializeProfilingConfig Drop the paragraph enumerating which exception types the vendored JSON reader throws. That catch (Exception) does not swallow Error is a language-level given, and listing the reader's internal exception types invites the comment to drift as that code changes. The rationale a reader cannot infer from the code — why the deserializer is called directly, and why null is returned instead of rethrowing — stays. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 337a9f1 commit 69bdef7

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Performance
66

77
- Remove an unused lock from `SentryPerformanceProvider`, which was allocated on every cold start in `ContentProvider.onCreate` without ever being acquired ([#5871](https://github.com/getsentry/sentry-java/pull/5871))
8+
- Parse the app start profiling config with only the deserializer it needs instead of building a full `JsonSerializer` and `SentryOptions`, cutting 188 of 221 allocations on the main thread before `Application.onCreate` ([#5867](https://github.com/getsentry/sentry-java/pull/5867))
89

910
## 8.51.0
1011

‎sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import io.sentry.ILogger;
1414
import io.sentry.ISentryLifecycleToken;
1515
import io.sentry.ITransactionProfiler;
16-
import io.sentry.JsonSerializer;
16+
import io.sentry.JsonObjectReader;
1717
import io.sentry.SentryAppStartProfilingOptions;
1818
import io.sentry.SentryExecutorService;
1919
import io.sentry.SentryLevel;
@@ -117,8 +117,7 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
117117
try (final @NotNull Reader reader =
118118
new BufferedReader(new InputStreamReader(new FileInputStream(configFile)))) {
119119
final @Nullable SentryAppStartProfilingOptions profilingOptions =
120-
new JsonSerializer(SentryOptions.empty())
121-
.deserialize(reader, SentryAppStartProfilingOptions.class);
120+
deserializeProfilingConfig(reader);
122121

123122
if (profilingOptions == null) {
124123
logger.log(
@@ -166,6 +165,25 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
166165
}
167166
}
168167

168+
/**
169+
* Parses the app start profiling config with only the deserializer it needs. Going through {@link
170+
* io.sentry.JsonSerializer} would allocate a full {@link SentryOptions} plus every registered
171+
* deserializer on the main thread before {@code Application.onCreate}, to use exactly one of
172+
* them.
173+
*
174+
* <p>Returns null on malformed input, matching what {@code JsonSerializer.deserialize} did, so
175+
* callers keep reporting it as a deserialization failure rather than a read error.
176+
*/
177+
private @Nullable SentryAppStartProfilingOptions deserializeProfilingConfig(
178+
final @NotNull Reader reader) {
179+
try (final @NotNull JsonObjectReader jsonReader = new JsonObjectReader(reader)) {
180+
return new SentryAppStartProfilingOptions.Deserializer().deserialize(jsonReader, logger);
181+
} catch (Exception e) {
182+
logger.log(SentryLevel.ERROR, "Error when deserializing", e);
183+
return null;
184+
}
185+
}
186+
169187
private void createAndStartContinuousProfiler(
170188
final @NotNull Context context,
171189
final @NotNull SentryAppStartProfilingOptions profilingOptions,

‎sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@ class SentryPerformanceProviderTest {
153153
)
154154
}
155155

156+
@Test
157+
fun `when config file is malformed, profiler is not started`() {
158+
fixture.getSut { config -> config.writeText("{\"profile_sampled\": tru") }
159+
assertNull(AppStartMetrics.getInstance().appStartProfiler)
160+
assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler)
161+
verify(fixture.logger).log(eq(SentryLevel.ERROR), eq("Error when deserializing"), any())
162+
verify(fixture.logger)
163+
.log(
164+
eq(SentryLevel.WARNING),
165+
eq(
166+
"Unable to deserialize the SentryAppStartProfilingOptions. App start profiling will not start."
167+
),
168+
)
169+
}
170+
156171
@Test
157172
fun `when profiling is disabled, profiler is not started`() {
158173
fixture.getSut { config ->

0 commit comments

Comments
 (0)