perf(android): Defer SentryFrameMetricsCollector thread startup#5641
Open
runningcode wants to merge 2 commits into
Open
perf(android): Defer SentryFrameMetricsCollector thread startup#5641runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
SentryFrameMetricsCollector created and started its HandlerThread in the constructor, blocking the calling thread (the main thread during SDK init) on HandlerThread.getLooper(). The handler is only needed once startCollection() registers a listener, so start the thread lazily there instead. Apps that never collect frame metrics no longer start the thread at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 18c0bc2 | 306.73 ms | 349.77 ms | 43.03 ms |
| 0eaac1e | 316.82 ms | 357.34 ms | 40.52 ms |
| d15471f | 303.49 ms | 439.08 ms | 135.59 ms |
| fc5ccaf | 276.52 ms | 370.46 ms | 93.93 ms |
| e2dce0b | 308.96 ms | 360.10 ms | 51.14 ms |
| 5b1a06b | 352.27 ms | 413.70 ms | 61.43 ms |
| 37ec571 | 366.04 ms | 424.28 ms | 58.23 ms |
| 9fbb112 | 361.43 ms | 427.57 ms | 66.14 ms |
| bbc35bb | 324.88 ms | 425.73 ms | 100.85 ms |
| ff8eea4 | 313.42 ms | 337.08 ms | 23.66 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 18c0bc2 | 1.58 MiB | 2.13 MiB | 557.33 KiB |
| 0eaac1e | 1.58 MiB | 2.19 MiB | 619.17 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| fc5ccaf | 1.58 MiB | 2.13 MiB | 557.54 KiB |
| e2dce0b | 0 B | 0 B | 0 B |
| 5b1a06b | 0 B | 0 B | 0 B |
| 37ec571 | 0 B | 0 B | 0 B |
| 9fbb112 | 1.58 MiB | 2.11 MiB | 539.18 KiB |
| bbc35bb | 1.58 MiB | 2.12 MiB | 553.01 KiB |
| ff8eea4 | 1.58 MiB | 2.28 MiB | 718.64 KiB |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves JAVA-535
📜 Description
SentryFrameMetricsCollectorcreated and started itsHandlerThreadin the constructor and then callednew Handler(handlerThread.getLooper()).getLooper()blocks the caller until the new thread's looper is ready — and the collector is constructed on the main thread duringSentryAndroid.init(loadDefaultAndMetadataOptions).The
handleris only ever used bytrackCurrentWindow(), which no-ops until a listener is registered viastartCollection(). So this starts theHandlerThreadlazily on the firststartCollection()(profiling /SpanFrameMetricsCollector). The constructor still registers the activity-lifecycle callback (current-window tracking is unchanged) but no longer blocks. Apps that never collect frame metrics never start the thread at all.Reusing the SDK's shared executor isn't viable here:
addOnFrameMetricsAvailableListenerrequires a dedicated non-main-threadHandler(Looper), which the sharedScheduledExecutorServicedoesn't provide — so the right move is to defer the dedicated thread, not share one.💡 Motivation and Context
From the customer-provided Perfetto trace (
sentryinvest): the main thread blocked ~5.75 ms onHandlerThread.getLooper()during init viaSentryFrameMetricsCollector.<init>.💚 How did you test it?
SentryFrameMetricsCollectorTest(incl. a new test assertinghandleris null after construction and non-null afterstartCollection); fullsentry-android-coresuite passes.Pixel 3 (Android 12) benchmark — ART method trace → Perfetto trace_processor:
HandlerThread.getLooper()Object.wait()The synchronous main-thread wait for the frame-metrics looper is entirely removed from construction.
Cold-start A/B —
sentry-samples-android(release, Pixel 3, 15 cold starts each viaam start -W -S,TotalTimems, first cold-disk run dropped):Directionally faster on every statistic, but within end-to-end cold-start noise. Note this sample auto-inits Sentry and calls
Sentry.startProfiler()inonCreate, so frame collection (the lazy-thread trigger) fires during startup either way — the change just moves when within startup. So this isn't a strong end-to-end showcase; the deterministic proof is the ART trace above. The real win is for apps that don't start frame collection at startup (the thread is then never created at init).📝 Checklist
sendDefaultPIIis enabled.