Repository navigation
feat(flutter): Revamp app-start frame spans - #4018
Conversation
Measure root attachment, framework builds, and first rasterization with shared bounded observations across both trace lifecycles. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Keep the Manual Replay category alongside the startup workloads. Co-Authored-By: Codex <noreply@openai.com>
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. Features
🤖 This preview updates automatically when you update the PR. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v10-branch #4018 +/- ##
=============================================
Coverage ? 88.77%
=============================================
Files ? 346
Lines ? 12531
Branches ? 0
=============================================
Hits ? 11124
Misses ? 1407
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Unify recorded intervals and simplify the app-start result model. Move Flutter frame attributes into proposed semantic conventions. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Bypass recording logic when no startup recorder is active. Only allocate a recorder for bindings that support observation. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Clarify timing names and simplify startup result handling. Remove redundant state and copying, consolidate overlapping tests, and assert the complete expected startup span list. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Remove the internal buffer counter and its span attribute. Keep the ten-build recording limit and its regression coverage. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Keep isolate enrichment for framework work and omit unsupported thread identity from raster spans. Remove hardcoded thread labels. Align recorder, result, and example names with their roles. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Derive the startup endpoint from the required result and replace redundant initialization timestamp state with a completion flag. Cover invalid first-raster timing at the handler boundary. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Use recorded intervals for native and Flutter startup work. Remove the phase model and reuse each trace's interval writer while preserving validation, cutoff filtering, and emission timing. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Pass raster and framework intervals directly to the trace. Keep the shared interval model and raster conversion with startup timing, and consolidate the associated tests. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Inline interval construction, create native timestamps in UTC, and skip end-clock reads once the frame buffer is full. Simplify raster validation and shorten implementation comments. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Inject a root-isolate helper into app-start trace tests so they also run on Chrome. Format the replay tests and generated JNI bindings with the stable Dart formatter used by CI. Refs #3838 Co-Authored-By: Codex <noreply@openai.com>
Preserve minimum idle endpoints across resumes and clarify recorder consumption and cleanup. Make example startup workloads opt-in and provide explicit binding documentation for dartdoc. Refs #4018 Co-Authored-By: Codex <noreply@openai.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved correctness, contract, filtering, timestamp, and compatibility issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Revamps Flutter standalone app-start tracing with phase-specific spans, shared timing for static and streaming traces, configurable example workloads, tests, and migration documentation.
Changes:
- Adds initialization, attachment, frame-build, and raster timing.
- Updates idle lifecycle handling and span operations.
- Expands tests and documents renamed spans and workloads.
File summaries
| File | Reviewed change |
|---|---|
packages/flutter/test/sentry_flutter_test.dart |
Tests initialization completion recording. |
packages/flutter/test/app_start/standalone/streaming_app_start_trace_test.dart |
Updates streaming trace coverage. |
packages/flutter/test/app_start/standalone/static_app_start_trace_test.dart |
Updates static trace coverage. |
packages/flutter/test/app_start/standalone/standalone_app_start_handler_test.dart |
Tests lifecycle and frame handling. |
packages/flutter/test/app_start/standalone/app_start_vitals_test.dart |
Updates app-start vitals fixtures. |
packages/flutter/test/app_start/root_isolate_helper.dart |
Adds root-isolate test support. |
packages/flutter/test/app_start/first_frame_timing.dart |
Adds deterministic frame timing fixtures. |
packages/flutter/test/app_start/app_start_timing_test.dart |
Tests timing and raster parsing. |
packages/flutter/test/app_start/app_start_recorder_test.dart |
Tests framework interval recording. |
packages/flutter/test/app_start/app_start_binding_test.dart |
Tests binding hooks. |
packages/flutter/test/app_start_trace_test_support.dart |
Updates trace test support. |
packages/flutter/README.md |
Links migration documentation. |
packages/flutter/MIGRATION.md |
Documents renamed spans and workloads. |
packages/flutter/lib/src/sentry_flutter.dart |
Records initialization completion. |
packages/flutter/lib/src/binding_wrapper.dart |
Captures attachment and build intervals. |
packages/flutter/lib/src/app_start/standalone/streaming_app_start_trace.dart |
Emits streaming app-start spans. |
packages/flutter/lib/src/app_start/standalone/static_app_start_trace.dart |
Emits static app-start spans. |
packages/flutter/lib/src/app_start/standalone/standalone_app_start_handler.dart |
Coordinates startup observations. |
packages/flutter/lib/src/app_start/standalone/app_start_trace.dart |
Defines the app-start trace contract. |
packages/flutter/lib/src/app_start/app_start_timing.dart |
Adds timing models. |
packages/flutter/lib/src/app_start/app_start_recorder.dart |
Records framework startup work. |
packages/flutter/example/test/screens/app_start_workloads_test.dart |
Tests configurable workloads. |
packages/flutter/example/test/home_screen_test.dart |
Tests workload display defaults. |
packages/flutter/example/README.md |
Documents example workloads. |
packages/flutter/example/lib/screens/app_start_workloads.dart |
Implements workload widgets and painters. |
packages/flutter/example/lib/main.dart |
Enables attachment workload configuration. |
packages/flutter/example/lib/home_screen.dart |
Adds workload display and raster overlay. |
packages/flutter/example/lib/app_config.dart |
Adds workload configuration flags. |
packages/dart/test/telemetry/span/idle_recording_sentry_span_v2_test.dart |
Tests paused idle completion. |
packages/dart/test/sentry_tracer_test.dart |
Tests tracer idle-end behavior. |
packages/dart/lib/src/telemetry/span/idle_recording_sentry_span_v2.dart |
Adds idle timeout pause/resume. |
packages/dart/lib/src/sentry_tracer.dart |
Adds minimum-end timestamp handling. |
packages/dart/lib/src/constants.dart |
Updates app-start operation constants. |
Review details
Suppressed comments (6)
packages/dart/lib/src/constants.dart:12
SentrySpanOperationsis re-exported from the publicpackages/dart/lib/sentry.dartbarrel, so deleting these old constants is a source-breaking change even though the class is marked@internal. The repository guidance prefers a deprecation path, while the migration note only covers wire-name changes; retain deprecated aliases or explicitly ship this as a major-version API removal.
static const String appStartPreInit = 'app.start.pre_init';
static const String appStartSentryInit = 'app.start.sentry_init';
static const String appStartRootWidgetAttachment =
'app.start.root_widget_attachment';
static const String appStartFrameBuild = 'app.start.frame_build';
static const String appStartFrameRaster = 'app.start.frame_raster';
packages/dart/lib/src/sentry_tracer.dart:132
_minimumEndTimestampis applied to everyfinish()call, including the explicit_root.finish(endTimestamp: _finalDeadlineTimestamp)inStaticAppStartTrace._finishAtDeadline. BecauserecordFirstFrameis intentionally allowed to race while that deadline drain awaits, a raster endpoint after the deadline can become the minimum and move the supposedly hard-deadline root past_finalDeadlineTimestamp. Restrict this clamp to idle completion or make the deadline/close path override it.
final minimumEnd = _minimumEndTimestamp;
if (minimumEnd != null && _rootEndTimestamp.isBefore(minimumEnd)) {
_rootEndTimestamp = minimumEnd;
}
packages/flutter/example/lib/screens/app_start_workloads.dart:9
State.initStateforMyAppruns when the root element is mounted and built during the first frame;attachToBuildOwneronly installs and schedules the root before that lifecycle callback. Consequently this workload will be attributed toFrame Build, notRoot Widget Attachment, so the example cannot exercise the phase it labels. Move the work to an actual binding attachment hook or relabel the configuration.
/// Runs synchronous catalog work during the root widget's initState.
void runRootAttachmentWorkload() {
// Root initState runs inside attachToBuildOwner, before frame layout.
final catalog =
packages/flutter/lib/src/app_start/standalone/app_start_trace.dart:113
- This contract only accepts framework intervals plus a raster interval, and both implementations emit those directly under
App Start. The linked issue's target tree still requires an always-onFirst Frame Renderparent (app.start.first_frame_render) spanning first vsync to raster finish, withVsync Overhead,Frame Build,Raster Handoff, andFrame Rasterizationbelow it. As written, the requested parent and tiled frame breakdown are missing.
/// startup endpoint. The root retains its existing idle/extension lifecycle.
void recordFirstFrame(
AppStartRecordedInterval rasterInterval, {
List<AppStartRecordedInterval> frameworkIntervals = const [],
});
packages/flutter/lib/src/app_start/standalone/app_start_trace.dart:106
- Recording the init-end timestamp alone cannot implement the linked contract's conditional
Post-Init Startupspan. This interface carries neither the app-runner signal nor the first-vsync endpoint, so the required init-end-to-first-vsync residual and its confidence/run-app attributes can never be emitted.
/// Records the end of SDK setup, before entering the app runner when one
/// is supplied. An app runner may still perform application work before
/// calling `runApp`.
void recordInitEnd(DateTime endTimestamp);
packages/flutter/test/app_start/app_start_timing_test.dart:133
- This adds a third independent top-level group to the file. The test guidelines require one top-level group per test file, so place the raster resolver cases under the existing
$AppStartTimingsubject group or split them into a separate mirrored test file.
group('tryResolveAppStartRasterInterval', () {
- Files reviewed: 33/33 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Release the idle hold after unusable first-frame observations without recording a measurement. Reject raster intervals before native startup and preserve streaming roots when initialization spans are filtered. Refs #4018 Co-Authored-By: Codex <noreply@openai.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.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4ef0de5. Configure here.
Validate the display endpoint independently of the raster interval. Keep valid initial-display timing when raster start precedes native startup, and cancel prepared tracking when no valid endpoint exists. Refs #4018 Co-Authored-By: Codex <noreply@openai.com>
Accept attached roots while first-frame submission is deferred. Record complete builds independently of root attachment. Address idle documentation and runner assertion review feedback. Preserve navigation coverage and remove migration notes. Refs #4018 Co-Authored-By: Codex <noreply@openai.com>

📜 Description
Split standalone app start into Pre-Init Startup, Sentry Initialization, Root Widget Attachment, Frame Build, and Frame Rasterization.
Static and streaming traces share the same measurements. Capture warm-up and deferred build work, with raster-only detail when binding hooks are unavailable. Includes configurable example workloads and migration notes for changed span names.
App-start span tree: before and after
The standalone root stays the same. The old broad first-frame interval is replaced by separate initialization, attachment, framework-build, and raster intervals. Native detail remains alongside them.
Before this PR
After this PR
The indented arrows describe intervals, not additional spans. All named phases are direct children of
App Start. Multiple warm-up/deferred builds can precede one raster span; there is no enclosing First Frame Render span or one-to-one build/raster pairing. Native detail may overlap Pre-Init Startup, so child durations are not additive.Attachment/build spans require Sentry's binding hooks; an existing standard binding can still provide raster timing when initialization precedes rendering. Explicit app-start extensions are unchanged and omitted from these trees. The legacy app-start spans attached to
ui.loadare also unchanged.💡 Motivation and Context
Make slow startup work attributable to the phase that caused it.
Refs #3838.
💚 How did you test it?
v10-branch; formatting and analysis checks passed.📝 Checklist
sendDefaultPiiis enabled🔮 Next steps
Run full CI and review the changed span names and attributes before release.