Skip to content

feat: Standalone app start tracing#7660

Open
philipphofmann wants to merge 52 commits intomainfrom
feat/standalone-app-start-tracing
Open

feat: Standalone app start tracing#7660
philipphofmann wants to merge 52 commits intomainfrom
feat/standalone-app-start-tracing

Conversation

@philipphofmann
Copy link
Copy Markdown
Member

@philipphofmann philipphofmann commented Mar 10, 2026

📜 Description

Send app start data as a standalone transaction instead of attaching it to the first UIViewController transaction.
Screenshot 2026-03-12 at 11 31 38

The measurement is passed directly via SentryTracerConfiguration, avoiding the global static and its race conditions.

We intentionally don't add public user-facing docs for this because this is more of a proof of concept for #6883 that we still need to iterate on before we want a larger audience to try it. The Sentry product doesn't handle these standalone app start transactions yet.

💡 Motivation and Context

Fixes #6883

💚 How did you test it?

  • Unit tests
  • End-to-end regression test: Ran the iOS-Swift sample app on a clean simulator with enableStandaloneAppStartTracing = false on this branch and compared against main. Both produce identical transaction structure (same operations, same hierarchy: Cold Start grouping span with 5 child phase spans under the ui.load transaction). No regression.
Regression test run 2 — 2026-03-12T10:42Z

Method

  1. Added a unique regression-test scope tag to identify each run
  2. Erased the simulator between runs for clean cold starts
  3. Built and launched iOS-Swift on each branch, waited ~15s, stopped the app
  4. Compared transactions via Sentry MCP (search_events + get_trace_details)

Results

Property Feature Branch Main Branch
Tag regression-test-feature-branch-2 regression-test-main-branch-2
Trace ID 2279393e2bdd4126a29efeda00dd946b 7a1bb60029c842fea30b0e289e507b81
Total spans 27 ¹ 26
App start spans 6 (Cold Start + 5 phases) 6 (Cold Start + 5 phases)
Transaction op ui.load ui.load

¹ The extra span is an unrelated profileLaunch file.write, not an app start span.

Full span comparison (sorted by duration desc)

Span Description Operation Feature Branch Main Branch
ErrorsViewController ui.load 1101ms 1051ms
ErrorsViewController initial display ui.load.initial_display 945ms 831ms
ErrorsViewController full display ui.load.full_display 945ms 831ms
Cold Start app.start.cold 945ms 831ms
Pre Runtime Init app.start.cold 465ms 478ms
POST http://localhost:8969/stream http.client 159ms 227ms
Application Init app.start.cold 177ms 139ms
UIKit Init app.start.cold 161ms 92ms
Initial Frame Render app.start.cold 141ms 120ms
loadView ui.load 3ms 5ms
Runtime Init to Pre Main Initializers app.start.cold 2ms 2ms
QmU-DD-itF-view-M6c-EX-0C6.nib file.read 0.7ms 0.5ms
data.data (533 bytes) file.write 0.5ms 0.2ms
Info.plist file.read 0.3ms 0.4ms
Info-8.0+.plist file.read 0.3ms 0.3ms
Sim.Touch.plist file.read 0.3ms 0.3ms
Sim.Pencil.plist file.read 0.2ms 0.3ms
viewDidAppear ui.load 0.3ms 0.2ms
layoutSubViews ui.load 0.2ms 0.2ms
viewDidLoad ui.load 0.2ms 0.2ms
layoutSubViews ui.load 0.2ms 0.2ms
viewWillLayoutSubviews ui.load 0.02ms 0.03ms
viewWillLayoutSubviews ui.load 0.02ms 0.02ms
viewWillAppear ui.load 0.02ms 0.02ms
viewDidLayoutSubviews ui.load 0.02ms 0.02ms
viewDidLayoutSubviews ui.load 0.02ms 0.02ms

Sentry links

Regression test run 1 — 2026-03-12T09:20Z

Method

  1. Added a unique regression-test scope tag to identify each run
  2. Erased the simulator between runs for clean cold starts
  3. Built and launched iOS-Swift on each branch, waited ~15s, stopped the app
  4. Compared transactions via Sentry MCP (search_events + get_trace_details)

Results

Property Feature Branch Main Branch
Tag regression-test-feature-branch regression-test-main-branch
Trace ID 3ff1c4e3c3754ab18ece26d7e0cf722a 3edc353029d34c9292f5ea85df4851ac
Total spans 26 26
App start spans 6 (Cold Start + 5 phases) 6 (Cold Start + 5 phases)
Transaction op ui.load ui.load

Full span comparison (sorted by duration desc)

Span Description Operation Feature Branch Main Branch
ErrorsViewController ui.load 1152ms 1091ms
ErrorsViewController initial display ui.load.initial_display 967ms 887ms
ErrorsViewController full display ui.load.full_display 967ms 920ms
Cold Start app.start.cold 967ms 887ms
Pre Runtime Init app.start.cold 468ms 531ms
POST http://localhost:8969/stream http.client 189ms 206ms
UIKit Init app.start.cold 188ms 102ms
Initial Frame Render app.start.cold 171ms 113ms
Application Init app.start.cold 137ms 138ms
loadView ui.load 4ms 5ms
Runtime Init to Pre Main Initializers app.start.cold 2ms 2ms
QmU-DD-itF-view-M6c-EX-0C6.nib file.read 0.5ms 0.6ms
Info.plist file.read 0.4ms 0.4ms
Info-8.0+.plist file.read 0.3ms 0.3ms
data.data (533 bytes) file.write 0.3ms 0.4ms
Sim.Pencil.plist file.read 0.3ms 0.2ms
layoutSubViews ui.load 0.3ms 0.3ms
Sim.Touch.plist file.read 0.3ms 0.2ms
viewDidAppear ui.load 0.2ms 0.2ms
viewDidLoad ui.load 0.2ms 0.2ms
layoutSubViews ui.load 0.07ms 0.08ms
viewWillLayoutSubviews ui.load 0.03ms 0.03ms
viewWillAppear ui.load 0.02ms 0.02ms
viewWillLayoutSubviews ui.load 0.02ms 0.02ms
viewDidLayoutSubviews ui.load 0.02ms 0.02ms
viewDidLayoutSubviews ui.load 0.02ms 0.02ms

Sentry links

Reproducible prompt
Run an end-to-end regression test for app start tracing. Compare the current branch
(with enableStandaloneAppStartTracing = false) against main to verify no regression
in the UIViewController app start transaction.

Steps:
1. In SentrySDKWrapper.swift, set enableStandaloneAppStartTracing = false and add a
   unique scope tag: scope.setTag(value: "regression-test-feature-branch", key: "regression-test")
2. Erase the iOS simulator (xcrun simctl shutdown <ID> && xcrun simctl erase <ID>),
   boot it, build and run iOS-Swift scheme, wait 15 seconds, stop the app
3. Revert sample app changes, stash, checkout main
4. In SentrySDKWrapper.swift on main, add scope tag:
   scope.setTag(value: "regression-test-main-branch", key: "regression-test")
5. Erase the simulator again, boot, build and run iOS-Swift, wait 15 seconds, stop
6. Revert main changes, checkout back to feature branch, pop stash
7. Use Sentry MCP search_events to find transactions with tag regression-test for
   each branch in project 5428557 (sentry-sdks org)
8. Use get_trace_details and search_events to get all spans for both traces
9. Compare: span count, span operations, span descriptions, hierarchy structure.
   Durations will differ (expected). Everything else should match.
10. Update the PR description "How did you test it?" section with the comparison.

📝 Checklist

You have to check all boxes before merging:

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 10, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • Standalone app start tracing by philipphofmann in #7660

Internal Changes 🔧

Samples

  • Restructure samples and revert to sample-specific target names by philprime in #7659
  • Restructure iOS-Swift6 sample by philprime in #7656

Other

  • (deps) Update clang-format version by github-actions in #7675

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 10, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 00e0d76

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.356%. Comparing base (8d9e501) to head (00e0d76).
⚠️ Report is 14 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #7660       +/-   ##
=============================================
+ Coverage   85.314%   85.356%   +0.042%     
=============================================
  Files          485       486        +1     
  Lines        28838     28901       +63     
  Branches     12521     12556       +35     
=============================================
+ Hits         24603     24669       +66     
+ Misses        4188      4183        -5     
- Partials        47        49        +2     
Files with missing lines Coverage Δ
Sources/Sentry/SentryAppStartMeasurementProvider.m 92.307% <100.000%> (+0.415%) ⬆️
Sources/Sentry/SentryBuildAppStartSpans.m 100.000% <100.000%> (ø)
Sources/Sentry/SentryTracer.m 99.026% <100.000%> (+0.768%) ⬆️
...es/Swift/Helper/SentryEnabledFeaturesBuilder.swift 100.000% <100.000%> (ø)
...s/AppStartTracking/AppStartReportingStrategy.swift 100.000% <100.000%> (ø)
...tions/AppStartTracking/SentryAppStartTracker.swift 93.209% <100.000%> (+0.128%) ⬆️
Sources/Swift/SentryDependencyContainer.swift 97.196% <100.000%> (+0.013%) ⬆️
Sources/Swift/SentryExperimentalOptions.swift 83.333% <100.000%> (+3.333%) ⬆️

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8d9e501...00e0d76. Read the comment docs.

@philipphofmann philipphofmann added the ready-to-merge Use this label to trigger all PR workflows label Mar 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 10, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1214.31 ms 1243.60 ms 29.30 ms
Size 24.14 KiB 1.13 MiB 1.10 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
b9e1a29 1216.41 ms 1255.80 ms 39.39 ms
d68691e 1221.48 ms 1248.13 ms 26.65 ms
c424b6a 1220.38 ms 1248.18 ms 27.80 ms
5b1e2a1 1217.63 ms 1245.49 ms 27.86 ms
c58a1c5 1229.49 ms 1251.19 ms 21.70 ms
2e5230b 1207.41 ms 1240.41 ms 33.00 ms
b0c71c8 1206.29 ms 1242.13 ms 35.84 ms
3461f50 1228.08 ms 1263.37 ms 35.29 ms
7e30a5e 1214.57 ms 1247.78 ms 33.21 ms
1b18a2d 1210.42 ms 1243.47 ms 33.05 ms

App size

Revision Plain With Sentry Diff
b9e1a29 24.14 KiB 1.11 MiB 1.09 MiB
d68691e 24.14 KiB 1.12 MiB 1.09 MiB
c424b6a 24.14 KiB 1.06 MiB 1.04 MiB
5b1e2a1 24.14 KiB 1.11 MiB 1.09 MiB
c58a1c5 24.14 KiB 1.11 MiB 1.09 MiB
2e5230b 24.14 KiB 1.04 MiB 1.02 MiB
b0c71c8 24.14 KiB 1.08 MiB 1.06 MiB
3461f50 24.14 KiB 1.11 MiB 1.09 MiB
7e30a5e 24.14 KiB 1.10 MiB 1.08 MiB
1b18a2d 24.14 KiB 1.10 MiB 1.07 MiB

Previous results on branch: feat/standalone-app-start-tracing

Startup times

Revision Plain With Sentry Diff
92fb0d9 1198.41 ms 1215.74 ms 17.33 ms
c1f77f9 1220.31 ms 1242.54 ms 22.23 ms
8b737d8 1222.43 ms 1252.96 ms 30.53 ms
edbdb50 1217.06 ms 1249.92 ms 32.86 ms
0d2ca60 1229.17 ms 1261.09 ms 31.91 ms
9175efb 1219.83 ms 1239.50 ms 19.67 ms

App size

Revision Plain With Sentry Diff
92fb0d9 24.14 KiB 1.13 MiB 1.10 MiB
c1f77f9 24.14 KiB 1.13 MiB 1.10 MiB
8b737d8 24.14 KiB 1.12 MiB 1.10 MiB
edbdb50 24.14 KiB 1.12 MiB 1.10 MiB
0d2ca60 24.14 KiB 1.12 MiB 1.10 MiB
9175efb 24.14 KiB 1.12 MiB 1.10 MiB

Copy link
Copy Markdown
Member Author

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

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

Review for Claude Code.

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 10, 2026

Sentry Build Distribution

App Version Configuration
App 9.6.0 (1) Release

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 10, 2026

Sentry Build Distribution

App Version Configuration
App 9.6.0 (1) Release

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 10, 2026

Sentry Build Distribution

App Name App ID Version Configuration Install Page
SDK-Size io.sentry.sample.SDK-Size 9.7.0 (1) Release Install Build

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 10, 2026

Sentry Build Distribution

App Version Configuration
App 9.6.0 (1) Release

Base automatically changed from ref/extract-app-start-measurement-provider to main March 11, 2026 10:44
…vider

Move the app start measurement retrieval logic from SentryTracer
into a standalone SentryAppStartMeasurementProvider class to
decouple it from the tracer and prepare for a future Swift
rewrite.

Agent transcript: https://claudescope.sentry.dev/share/-ySeYAQr0t_-TzVJYh7grS4nvSpo6f1NKAIEUc4__60
Call SentryAppStartMeasurementProvider.reset() directly from
ClearTestState and the provider tests instead of routing
through SentryTracer's forwarding method.

Agent transcript: https://claudescope.sentry.dev/share/L1InLC1w4Xm6HgEp2bHk-sJ2wtr9fRfQ08BpwGE4JSs
Remove unused profilerReferenceID parameter, add header
docs, and fix stale TSan suppression.

Agent transcript: https://claudescope.sentry.dev/share/YCbJaiw4eo47tyRGdPdNfyWa3VZueptJtPoevH1S97A
Fix trailing closure ambiguity with DispatchWorkItem and remove
non-existent profilerReferenceID parameter.

Agent transcript: https://claudescope.sentry.dev/share/boilLz21_S-f8uu_dcXvext-dAi2MAY8xkbK2zEafW4
Add experimental option to send standalone app start
transactions instead of attaching app start data to the
first UIViewController transaction. Uses a strategy
pattern via AppStartMeasurementHandler protocol.

The standalone handler is currently a no-op placeholder;
actual transaction logic will be added in a follow-up.

Refs: #6883

Agent transcript: https://claudescope.sentry.dev/share/KZR39vRrsVDqgpm56ONILZFlu1u_gpBhAnK7QrpyOH0
Create a real tracer with app.start.cold/warm operation
that reuses the existing tracer pipeline for span building,
measurements, context, debug images, and profiling.

- Add SentrySpanOperationAppStartCold/Warm constants
- Relax getAppStartMeasurement to accept app start ops
- Skip intermediate root span for standalone transactions
- Enable option in iOS sample app for validation

Refs: #6883

Agent transcript: https://claudescope.sentry.dev/share/UaCuhStnxjQCwKH46hOsPqgeHsckPh15A0Flqw8JOPc
Move the standalone app start detection from
SentryBuildAppStartSpans to SentryTracer and pass it
as a BOOL parameter. Use span operation constants
instead of string literals.

Agent transcript: https://claudescope.sentry.dev/share/QyuEyImC81GP83tCrqUbp939ejQ3-3y_0TcwnvcejsA
Deduplicate the app start operation check in SentryTracer
into a single method reused in toTransaction and
getAppStartMeasurement.

Agent transcript: https://claudescope.sentry.dev/share/bffTUQ5TAcp6oTyLqR8SSSjLLG4pSUsZ8l7TB7qaq1s
Also verify the trace origin is auto.app_start to more
precisely identify standalone app start transactions.

Agent transcript: https://claudescope.sentry.dev/share/HhoCIcB8Nh0dEd16RtFdllS_ZIxK393KCEPaOUiPp5s
Add StandaloneAppStartTransactionHelper to centralize
the logic for identifying standalone app start
transactions next to the code that creates them.

Agent transcript: https://claudescope.sentry.dev/share/th338Y1YxxM5471m3BNGV5Az6QKNTNQzAyhCyZ9MmuM
Avoids race conditions with global static by passing the
measurement directly to the tracer.

Agent transcript: https://claudescope.sentry.dev/share/5dFraoe7qfSofcTRtjfi59JzjPmHtWPyBK9YLzp61_g
- Move public functions to top of SentryBuildAppStartSpans.m
- Expand SentryTracer comment on race condition avoidance
- Use string literals instead of constants in tests
- Assert specific debug image properties in test
- Add span tree examples to header doc comments

Agent transcript: https://claudescope.sentry.dev/share/4RQyqJ6qxJ3uLrCpOW-w1LjUpmtGerVFCS6DIZgKRXE
The platform guards for app start cleanup in clearTestState were
missing os(visionOS), causing appStartMeasurementRead to never
reset between tests on visionOS. This made tests that depend on
app start measurement data fail when run after other tests that
consume the measurement.

Agent transcript: https://claudescope.sentry.dev/share/XRVuiHsfZrr_PPrwGDvT9RkHPcp6AxGP1tIDaq2H4CU
@philipphofmann philipphofmann force-pushed the feat/standalone-app-start-tracing branch from 5378661 to 027eef2 Compare March 11, 2026 11:10
Reorder static helpers before public functions so forward
declarations are unnecessary.

Agent transcript: https://claudescope.sentry.dev/share/YxFRa6oUZjO-FbIdZ6tJm3NS3crlLEOkaJhSoyTlnnI
Group it with the other SENTRY_HAS_UIKIT methods at the bottom
of the file.

Agent transcript: https://claudescope.sentry.dev/share/l1p-jS5wvAvglbGGB7D_m6tnGYaagdHBQCTAddXclW0
- Protocol: AppStartMeasurementHandler → AppStartReportingStrategy
- AttachAppStartMeasurementHandler → AttachToTransactionStrategy
- SendStandaloneAppStartTransaction → StandaloneTransactionStrategy
- Method: handle() → report()
- Variable: measurementHandler → reportingStrategy
- Fix trailing slash typo in changelog entry
- Restore enableUncaughtNSExceptionReporting in sample app
- Make StandaloneAppStartTransactionHelper final
- Add test for unknown app start type in StandaloneTransactionStrategy
- Restore visionOS platform guard in measurement provider tests
- Rename tests to follow test_when_should convention
- Remove extra blank line in sentryBuildAppStartSpansInternal
Replace the class-level tearDown with per-test cleanup:
- AttachToTransactionStrategy tests: reset app start measurement
- StandaloneTransactionStrategy tests: reset current hub
- Integration tests: full clearTestState via setUpIntegrationHub
- Helper tests: no cleanup needed
- Rename tests to follow test_when_should naming convention
- Fix assertion order: value first, expected second
- Remove unnecessary force_unwrapping swiftlint disable
Verify the full path from SentryAppStartTracker through
StandaloneTransactionStrategy captures a transaction with the
correct name and operation, and does not set the global static.
Cover nil config measurement, nil measurement input,
unknown start type, enhanced integration assertions,
and default feature flag behavior.

Agent transcript: https://claudescope.sentry.dev/share/DHthmP4l-WXLLOSec8mR7LkoGRxh5KdqGZSeF3UaXik
Remove enableStandaloneAppStartTracing from sample app since
the feature isn't ready for general use yet. Restore visionOS
in #endif comment to match the opening platform guard.

Agent transcript: https://claudescope.sentry.dev/share/vz0QO06DfJpC3-St_cH3Br5qoqAuE2iHNNDFfK-M7uQ
@philipphofmann

This comment was marked as outdated.

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

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Send standalone app start transactions

1 participant