feat(extend-app-start): [3/4] Eagerly create the extended app start transaction#5608
feat(extend-app-start): [3/4] Eagerly create the extended app start transaction#5608buenaflor wants to merge 37 commits into
Conversation
|
📲 Install BuildsAndroid
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| db5be2f | 311.84 ms | 365.94 ms | 54.10 ms |
| 388ffc5 | 315.18 ms | 362.43 ms | 47.25 ms |
| d8041d7 | 305.32 ms | 370.59 ms | 65.27 ms |
| ca0c0cf | 318.45 ms | 370.06 ms | 51.61 ms |
| 201a6fd | 326.00 ms | 371.92 ms | 45.92 ms |
| b76203f | 336.02 ms | 403.42 ms | 67.40 ms |
| ba0608b | 319.63 ms | 373.04 ms | 53.41 ms |
| 682e090 | 347.33 ms | 427.28 ms | 79.95 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| db5be2f | 0 B | 0 B | 0 B |
| 388ffc5 | 0 B | 0 B | 0 B |
| d8041d7 | 0 B | 0 B | 0 B |
| ca0c0cf | 0 B | 0 B | 0 B |
| 201a6fd | 0 B | 0 B | 0 B |
| b76203f | 0 B | 0 B | 0 B |
| ba0608b | 0 B | 0 B | 0 B |
| 682e090 | 0 B | 0 B | 0 B |
Previous results on branch: feat/app-start-extension-materialize
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 69caaee | 313.73 ms | 373.92 ms | 60.19 ms |
| e5800ed | 318.21 ms | 375.48 ms | 57.27 ms |
| ae82ca0 | 381.62 ms | 426.12 ms | 44.51 ms |
| a037262 | 318.06 ms | 364.59 ms | 46.52 ms |
| 511890d | 315.38 ms | 376.00 ms | 60.62 ms |
| 20bbe9a | 305.48 ms | 387.25 ms | 81.77 ms |
| 4451634 | 321.21 ms | 376.21 ms | 55.00 ms |
| 3a27919 | 371.17 ms | 447.31 ms | 76.14 ms |
| 4cf89ba | 340.51 ms | 416.92 ms | 76.41 ms |
| 44c06ea | 324.19 ms | 384.88 ms | 60.69 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 69caaee | 0 B | 0 B | 0 B |
| e5800ed | 0 B | 0 B | 0 B |
| ae82ca0 | 0 B | 0 B | 0 B |
| a037262 | 0 B | 0 B | 0 B |
| 511890d | 0 B | 0 B | 0 B |
| 20bbe9a | 0 B | 0 B | 0 B |
| 4451634 | 0 B | 0 B | 0 B |
| 3a27919 | 0 B | 0 B | 0 B |
| 4cf89ba | 0 B | 0 B | 0 B |
| 44c06ea | 0 B | 0 B | 0 B |
54be119 to
d2b96ad
Compare
0f8ee4d to
5dfb0e1
Compare
5dfb0e1 to
0bd259a
Compare
5dd39af to
3bc1643
Compare
0bd259a to
0b6ada0
Compare
3bc1643 to
45f6c0b
Compare
c3f3627 to
cbaef2f
Compare
…tion (standalone-only) Registers an extend-listener on AppStartExtension that eagerly creates the standalone app.start transaction + extended child span in Application.onCreate, held open via waitForChildren until Sentry.finishAppStart() or the deadline. The first activity continues the eager trace into ui.load (attaching the screen so it stays a foreground app.start) instead of creating a second app.start; headless finishes the eager txn. The app start vital is max(natural, extended) so an early finish never shortens it, and is suppressed on deadline. Standalone-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2d5188e to
01b1dc4
Compare
cbaef2f to
d996425
Compare
…on extendAppStart Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… coverage Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd trim comments Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
…app start path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
…xtension-materialize
…t screen While the extension was open, every activity's onActivityCreated re-attached app.vitals.start.screen to the eager app.start, so a second activity opened before finishExtendedAppStart() overwrote the launch screen. Gate the attach on isAppStart so only the launch activity sets it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lize' into feat/app-start-extension-materialize # Conflicts: # sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java
…xtension-materialize
…xtension-materialize
Remove comments that restated the adjacent test code (the eager extendAppStart call and the second-activity open). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
…xtension-materialize
…xtension-materialize
The test names describe the scenarios; the inline comments restated them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xtension-materialize
…xtension-materialize
…dAppStartSpan is nullable Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The get-then-clear of the app start sampling decision in onExtendAppStartRequested looks redundant without context. Document that the decision is pre-rolled on the previous run, forces the eager app.start transaction's sampling and profiler binding, and must be cleared so the first ui.load can't also claim it. Co-Authored-By: Claude <noreply@anthropic.com>
…ager extension finishes The eager extendAppStart path persists trace headers so a later ui.load can continue the app.start trace, but never recorded the app start end time - that was only set on the headless path. If the extended transaction finished (user finish or deadline) before any activity, the first ui.load treated the continuation window as unbounded and joined the completed app-start trace no matter how much later it started. Persist the transaction's finish date via the transaction finished callback, which covers every finish path (finishExtendedAppStart, first frame, deadline), so the existing continuation window check bounds the extend path the same way it bounds the headless one. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9196ec9. Configure here.
romtsn
left a comment
There was a problem hiding this comment.
LGTM, but we really need to refactor this thing 🙈 it's just too many if-elses in here. If you got time could you task Fable to come up with nice a refactor plan maybe and create an issue out of that?
yeah :/ so many edge cases with all the different combinations, prolly some of the complexity will be gone as well once we make standalone the only way to go.
👍 |
…ension is active A first activity arriving more than a minute after launch resets the app start span to the activity create time and flips the type to warm. While an app start extension is active this corrupts the extended vital: the eager app.start transaction stays anchored at process start while the measurement subtracts the reset span start, and a cold launch gets reported under the warm key. An active extension is the user explicitly saying the launch is still in progress, so let it pin the launch: skip the reclassification while the extension is active and apply the heuristic as before once it has finished. Co-Authored-By: Claude <noreply@anthropic.com>

PR Stack (Extend App Start)
📜 Description
Registers the extend-listener and wires the extension through to the trace and the vital, making the feature work end-to-end. The extension is eager and standalone-only — only the standalone
app.starttransaction is independent of an activity and can be created inApplication.onCreate.ActivityLifecycleIntegrationenableStandaloneAppStartTracingis on (this is what makes the API standalone-only). OnSentry.extendAppStart()the listener eagerly creates the standaloneapp.starttransaction (waitForChildren+ deadline) and itsapp.start.extendedchild, and returns them as anExtendedAppStart.PerformanceAndroidEventProcessormax(natural first-frame duration, extended end), so an early finish never shortens it. On deadline the measurement is suppressed entirely (no inflated ~30s value); spans still attach. Non-extended starts are unchanged.Notes
appStartTransactionfield, so per-activity cleanup can't cancel it.ui.load(viaisActive()) instead of starting a secondapp.start, and attaches the screen so the foregroundapp.startkeeps itsapp.vitals.start.screen.waitForChildrenholds it open untilSentry.finishExtendedAppStart(). Headless finishes it at the headless stop time. A sharedcreateStandaloneAppStartTransaction(...)helper backs both paths.ui.load) extension path are removed — eager creation makes them unnecessary.💡 Motivation and Context
Part of the app start extension API stack (#5553). This is where the extension actually affects the trace and the vital, scoped to standalone app start tracing because only the standalone
app.starttransaction can be created eagerly inonCreate.💚 How did you test it?
Unit tests (TDD):
ActivityLifecycleIntegrationTest— eagerapp.start+ extended child on extend; first activity continues the trace with no secondapp.startand the screen attached; standalone & headless stay open untilfinishExtendedAppStart(); standalone-off is a no-op; the eager txn survives activity destroy.PerformanceAndroidEventProcessorTest— extended end drives the cold measurement; an early finish never reports shorter than the first-frame duration; a deadline finish suppresses the measurement.apiCheck, spotless, and the:sentry-android-coreunit suite pass.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
[4/4]— add the publicSentry.extendAppStart()/Sentry.finishExtendedAppStart()/Sentry.getExtendedAppStartSpan()facade.#skip-changelog