Skip to content

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

Merged
runningcode merged 4 commits into
mainfrom
no/java-621-app-start-profiling-config-deserialization
Jul 30, 2026
Merged

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

Conversation

@runningcode

@runningcode runningcode commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

📜 Description

JsonSerializer is a massive object with 60 serializers but we only need one of them in SentryPerformanceProvider. We can't re-use the one held in SentryOptions because the SentryPerformanceProvider content provider has a higher priority than the SentryInitProvider.
So instead we create a serializer with only the serializer we need for this specific purpose.

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 all those 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

Perf improvement

Related: #5708 and #5709 were previously closed for the same "small allocation win, not worth the trade off" reason.

GH Issue: #5707
Linear: #5707

💚 How did you test it?

Unit tests

📝 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

createAndStartContinuousProfiler 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.

@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.51.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 317.65 ms 360.98 ms 43.33 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
6b019b7 343.31 ms 417.23 ms 73.91 ms
d15471f 342.08 ms 415.44 ms 73.35 ms
8687935 332.52 ms 362.23 ms 29.71 ms
5b1a06b 352.27 ms 413.70 ms 61.43 ms
91bb874 314.47 ms 440.00 ms 125.53 ms
0ee65e9 321.06 ms 361.24 ms 40.18 ms
e63ad34 323.67 ms 390.33 ms 66.67 ms
33a08cc 267.08 ms 340.45 ms 73.37 ms
27d7cf8 397.90 ms 498.65 ms 100.75 ms
ee747ae 405.43 ms 485.70 ms 80.28 ms

App size

Revision Plain With Sentry Diff
6b019b7 0 B 0 B 0 B
d15471f 1.58 MiB 2.13 MiB 559.54 KiB
8687935 1.58 MiB 2.19 MiB 619.17 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
e63ad34 0 B 0 B 0 B
33a08cc 1.58 MiB 2.12 MiB 555.28 KiB
27d7cf8 1.58 MiB 2.12 MiB 549.42 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB

Previous results on branch: no/java-621-app-start-profiling-config-deserialization

Startup times

Revision Plain With Sentry Diff
7354f33 322.00 ms 364.18 ms 42.18 ms
5f23b7f 416.94 ms 486.71 ms 69.77 ms

App size

Revision Plain With Sentry Diff
7354f33 0 B 0 B 0 B
5f23b7f 0 B 0 B 0 B

runningcode and others added 3 commits July 30, 2026 11:21
…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 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>
…onfig

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>
@runningcode
runningcode force-pushed the no/java-621-app-start-profiling-config-deserialization branch from 162d4db to 8e27d0c Compare July 30, 2026 09:22
@runningcode
runningcode marked this pull request as ready for review July 30, 2026 09:27

@0xadam-brown 0xadam-brown left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ftw!

@romtsn romtsn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

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>
@runningcode
runningcode enabled auto-merge (squash) July 30, 2026 09:48
@runningcode
runningcode merged commit 69bdef7 into main Jul 30, 2026
70 of 71 checks passed
@runningcode
runningcode deleted the no/java-621-app-start-profiling-config-deserialization branch July 30, 2026 09:57
@github-actions

Copy link
Copy Markdown
Contributor
Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Performance

- Parse app start profiling config without JsonSerializer ([#5867](https://github.com/getsentry/sentry-java/pull/5867))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against b296f38

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.

3 participants