Skip to content

perf(android): Parse app start profiling config without JsonSerializer - #5867

Draft
runningcode wants to merge 2 commits into
mainfrom
no/java-621-app-start-profiling-config-deserialization
Draft

perf(android): Parse app start profiling config without JsonSerializer#5867
runningcode wants to merge 2 commits into
mainfrom
no/java-621-app-start-profiling-config-deserialization

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

📜 Description

SentryPerformanceProvider.launchAppStartProfiler parses the app start profiling config in ContentProvider.onCreate — main thread, before Application.onCreate, on every cold start. It did so via:

new JsonSerializer(SentryOptions.empty()).deserialize(reader, SentryAppStartProfilingOptions.class);

JsonSerializer.deserialize(Reader, Class) only ever reads options.getLogger() on this path, but its constructor builds a HashMap and allocates 56 deserializer instances to use exactly one of them — and SentryOptions.empty() is itself 63 allocations.

This replaces both with the one deserializer the path needs, which is already the established idiom in ~20 places in this codebase (SpanContext, Contexts, SentryBaseEvent, …). Malformed input still returns null and logs "Error when deserializing", so callers keep reporting it as a deserialization failure rather than a read error.

Measured on a Pixel 10 (API 37) with androidx Microbenchmark, release build:

allocations time (median)
via JsonSerializer (before) 221 7548 ns
direct Deserializer (after) 33 3687 ns

allocationCount is deterministic, so the allocation figures are exact. The timings are steady-state (JIT-warmed, clocks unlocked), so they understate the real cold-start saving — warmup amortizes away the class loading and verification of those 56 deserializer classes, which the real one-shot path pays in full. No claim is made about measurable end-to-end startup improvement; ~3.9 µs is small against a ~1 s cold start. The point is main-thread work removed from before Application.onCreate.

💡 Motivation and Context

Fell out of researching JAVA-621 ("lazy-allocate SentryOptions sub-options"), which is now closed as won't-do — see the measurement writeup on that issue. In short: the sub-options are only ~20 of the 63 allocations in SentryOptions.empty(), and they are read unconditionally during SentryAndroid.init (ManifestMetadataReader's only guard is if (metadata != null); the replay block reads its own values back as readBool/isEmpty()/size() defaults) — so lazy holders get forced on first init anyway, even with replay/logs/metrics disabled. This call site was the actual win hiding behind that issue.

Related: JAVA-622 and JAVA-623 were previously closed for the same "small allocation win, not worth the trade off" reason.

GH Issue: #5707
Linear: JAVA-621

💚 How did you test it?

  • Existing SentryPerformanceProviderTest (19 cases) passes unchanged. It writes the config with JsonSerializer.serialize and now reads it back with the direct deserializer, so it exercises a real round-trip across both implementations.
  • Added when config file is malformed, profiler is not started, covering the truncated-JSON path that previously relied on JsonSerializer's catch-all — asserts both the ERROR log and the existing WARNING.
  • Full :sentry-android-core:testReleaseUnitTest suite green.
  • Allocation/timing deltas measured on device as above.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

SentryPerformanceProvider:177 also calls SentryOptions.empty(), for TracesSampler. That one genuinely needs a mutable options object (setProfileSessionSampleRate) and only runs when a sampled continuous-profiling config exists, so it is deliberately left alone.

…r (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 56 deserializers 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>
@linear-code

linear-code Bot commented Jul 29, 2026

Copy link
Copy Markdown

JAVA-621

@sentry

sentry Bot commented Jul 29, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.50.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 322.00 ms 364.18 ms 42.18 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
6b019b7 319.84 ms 333.15 ms 13.31 ms
c3ee041 310.64 ms 361.90 ms 51.26 ms
d501a7e 348.06 ms 431.42 ms 83.36 ms
3998a95 415.94 ms 478.54 ms 62.60 ms
ed33deb 343.30 ms 362.41 ms 19.10 ms
5b1a06b 310.56 ms 362.79 ms 52.22 ms
91bb874 310.68 ms 359.24 ms 48.56 ms
0ee65e9 317.37 ms 366.50 ms 49.13 ms
6edfca2 305.52 ms 432.78 ms 127.26 ms
5e269de 292.83 ms 379.12 ms 86.29 ms

App size

Revision Plain With Sentry Diff
6b019b7 0 B 0 B 0 B
c3ee041 0 B 0 B 0 B
d501a7e 0 B 0 B 0 B
3998a95 1.58 MiB 2.10 MiB 532.96 KiB
ed33deb 1.58 MiB 2.13 MiB 559.52 KiB
5b1a06b 0 B 0 B 0 B
91bb874 1.58 MiB 2.13 MiB 559.07 KiB
0ee65e9 0 B 0 B 0 B
6edfca2 1.58 MiB 2.13 MiB 559.07 KiB
5e269de 0 B 0 B 0 B

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant