Skip to content

feat: Standalone app start tracing#7660

Open
philipphofmann wants to merge 64 commits into
mainfrom
feat/standalone-app-start-tracing
Open

feat: Standalone app start tracing#7660
philipphofmann wants to merge 64 commits into
mainfrom
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 d731019

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

Codecov Report

❌ Patch coverage is 99.43182% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 86.188%. Comparing base (66415de) to head (d731019).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...s/AppStartTracking/AppStartReportingStrategy.swift 97.500% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #7660       +/-   ##
=============================================
- Coverage   86.192%   86.188%   -0.005%     
=============================================
  Files          487       488        +1     
  Lines        29651     29793      +142     
  Branches     12894     12950       +56     
=============================================
+ Hits         25557     25678      +121     
- Misses        4046      4065       +19     
- Partials        48        50        +2     
Files with missing lines Coverage Δ
Sources/Sentry/SentryAppStartMeasurementProvider.m 94.230% <100.000%> (+2.338%) ⬆️
Sources/Sentry/SentryBuildAppStartSpans.m 100.000% <100.000%> (ø)
.../SentryDefaultUIViewControllerPerformanceTracker.m 97.784% <100.000%> (+0.007%) ⬆️
Sources/Sentry/SentryPerformanceTracker.m 99.224% <100.000%> (+0.102%) ⬆️
Sources/Sentry/SentryTracer.m 97.393% <100.000%> (+0.129%) ⬆️
Sources/Sentry/SentryTransactionContext.m 83.478% <100.000%> (+2.105%) ⬆️
...es/Swift/Helper/SentryEnabledFeaturesBuilder.swift 100.000% <100.000%> (ø)
...tions/AppStartTracking/SentryAppStartTracker.swift 93.902% <100.000%> (+0.191%) ⬆️
Sources/Swift/Options.swift 100.000% <ø> (ø)
Sources/Swift/SentryDependencyContainer.swift 97.285% <100.000%> (+0.012%) ⬆️
... and 2 more

... and 6 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 66415de...d731019. 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 1216.00 ms 1249.29 ms 33.29 ms
Size 24.14 KiB 1.16 MiB 1.14 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
29d546e 1224.06 ms 1257.05 ms 32.98 ms
e56c394 1229.37 ms 1245.86 ms 16.49 ms
a7c42d9 1217.25 ms 1253.98 ms 36.73 ms
787537a 1218.35 ms 1251.72 ms 33.38 ms
7814719 1220.98 ms 1254.14 ms 33.16 ms
1155f0c 1222.41 ms 1251.18 ms 28.76 ms
6b08499 1231.61 ms 1241.90 ms 10.29 ms
9b154ad 1218.94 ms 1253.60 ms 34.66 ms
bdd8e0e 1233.35 ms 1266.96 ms 33.60 ms
670aba7 1219.98 ms 1257.20 ms 37.23 ms

App size

Revision Plain With Sentry Diff
29d546e 24.14 KiB 1.15 MiB 1.13 MiB
e56c394 24.14 KiB 1.16 MiB 1.13 MiB
a7c42d9 24.14 KiB 1.15 MiB 1.13 MiB
787537a 24.14 KiB 1.15 MiB 1.12 MiB
7814719 24.14 KiB 1.15 MiB 1.13 MiB
1155f0c 24.14 KiB 1.16 MiB 1.13 MiB
6b08499 24.14 KiB 1.15 MiB 1.13 MiB
9b154ad 24.14 KiB 1.16 MiB 1.13 MiB
bdd8e0e 24.14 KiB 1.16 MiB 1.13 MiB
670aba7 24.14 KiB 1.16 MiB 1.13 MiB

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

Startup times

Revision Plain With Sentry Diff
8b737d8 1222.43 ms 1252.96 ms 30.53 ms
c1f77f9 1220.31 ms 1242.54 ms 22.23 ms
0d2ca60 1229.17 ms 1261.09 ms 31.91 ms
50bee10 1228.36 ms 1267.13 ms 38.77 ms
edbdb50 1217.06 ms 1249.92 ms 32.86 ms
92fb0d9 1198.41 ms 1215.74 ms 17.33 ms
9175efb 1219.83 ms 1239.50 ms 19.67 ms
e03b996 1222.64 ms 1254.96 ms 32.32 ms
89b00b8 1218.33 ms 1255.14 ms 36.81 ms
ad22b9d 1214.31 ms 1243.60 ms 29.30 ms

App size

Revision Plain With Sentry Diff
8b737d8 24.14 KiB 1.12 MiB 1.10 MiB
c1f77f9 24.14 KiB 1.13 MiB 1.10 MiB
0d2ca60 24.14 KiB 1.12 MiB 1.10 MiB
50bee10 24.14 KiB 1.16 MiB 1.13 MiB
edbdb50 24.14 KiB 1.12 MiB 1.10 MiB
92fb0d9 24.14 KiB 1.13 MiB 1.10 MiB
9175efb 24.14 KiB 1.12 MiB 1.10 MiB
e03b996 24.14 KiB 1.16 MiB 1.14 MiB
89b00b8 24.14 KiB 1.16 MiB 1.13 MiB
ad22b9d 24.14 KiB 1.13 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.

Comment thread Sources/Sentry/SentryBuildAppStartSpans.m
Comment thread Sources/Sentry/SentryTracer.m Outdated
Comment thread Sources/Sentry/include/SentryBuildAppStartSpans.h
@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

📲 Install Builds

iOS

🔗 App Name App ID Version Configuration
SDK-Size io.sentry.sample.SDK-Size 9.13.0 (1) Release

⚙️ sentry-cocoa Build Distribution Settings

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


NSString *const SentrySpanOperationUiAction = @"ui.action";
NSString *const SentrySpanOperationUiActionClick = @"ui.action.click";
NSString *const SentrySpanOperationAppStartCold = @"app.start.cold";
Copy link
Copy Markdown
Member

@romtsn romtsn Apr 13, 2026

Choose a reason for hiding this comment

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

we've made a decision with @noahsmartin that we want to switch the span operation to just app.start, and make the distinction of the type by introducing these new attributes (as mentioned here):

app.vitals.start.type — app start type (cold/warm)
app.vitals.start.value - app start value in ms

this would make it cleaner as well as allow us to sample this specific spans/transactions in a custom TracesSampler callback without introducing a new sample rate.

Copy link
Copy Markdown
Contributor

@buenaflor buenaflor Apr 14, 2026

Choose a reason for hiding this comment

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

side note: for now we will additionally send app.vitals.start.cold/warm.value attributes on top of the app.vitals.start.value and app.vitals.start.type combination (getsentry/sentry-conventions#318).

This is mainly due to migrations because relay needs to manually backfill those attributes and existing transactions dont have them (see PR). So the mobile vitals dashboard will work with app.vitals.start.cold/warm.value for now. After ~90 days (which should be around end of July) we can fully migrate to app.vitals.start.value and app.vitals.start.type.

Depending on when this PR finishes we might not need the support for app.vitals.start.cold/warm.value :)

@itaybre itaybre requested a review from NinjaLikesCheez as a code owner May 11, 2026 21:30
Comment thread Tests/SentryTests/Transaction/SentryTracerTests.swift
Comment thread Sources/Sentry/SentryBuildAppStartSpans.m
Comment thread Sources/Swift/Integrations/AppStartTracking/AppStartReportingStrategy.swift Outdated
Comment thread Tests/SentryTests/Transaction/SentryTracerTests.swift Outdated
Comment thread CHANGELOG.md
itaybre added 3 commits May 11, 2026 18:53
The assertion checked "app.vitals.start.warm.value" which is a span
data key only set in standalone mode. The ui.load transaction uses
"app_start_warm" as its measurement key.
Comment thread Sources/Swift/Integrations/AppStartTracking/AppStartReportingStrategy.swift Outdated
Comment on lines +37 to +47

let hub = SentrySDKInternal.currentHub()
let tracer = hub.startTransaction(
with: context,
bindToScope: false,
customSamplingContext: [:],
configuration: configuration
)
tracer.origin = SentryTraceOriginAutoAppStart

tracer.finish()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

side note: if UI is rendered as part of app launch, the transaction should additionally have the app.vitals.start.screen attribute

Comment thread Sources/Sentry/SentryPerformanceTracker.m Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 39f4d4c. Configure here.

Comment thread Sources/Sentry/SentryPerformanceTracker.m
itaybre added 2 commits May 12, 2026 17:18
SentryAppStartMeasurementProvider is only available on UIKit
platforms. Without the guard, macOS builds fail.
Use a named ObjC category (SwiftPrivate) with rawNameSource: to
give a distinct selector, replacing the standalone C functions and
eliminating SentryTransactionContextFactory.h.
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

4 participants