Skip to content

feat(flutter): Revamp app-start frame spans - #4018

Merged
buenaflor merged 16 commits into
v10-branchfrom
feat/app-start-span-revamp
Sep 21, 2026
Merged

buenaflor merged 16 commits into
v10-branchfrom
feat/app-start-span-revamp

Conversation

@buenaflor

@buenaflor buenaflor commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

📜 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

App Start [app.start]
├── App start to plugin registration [app.start.plugin_registration]
│   └─ native startup → plugin attachment
├── Before Sentry Init Setup [app.start.sentry_setup]
│   └─ plugin attachment → Sentry init start
├── First frame render [app.start.first_frame_render]
│   └─ Sentry init start → first raster finish
└── Native detail spans [app.start.native]

After this PR

App Start [app.start]
├── Pre-Init Startup [app.start.pre_init]
│   └─ native startup → Sentry init start
├── Sentry Initialization [app.start.sentry_init]
│   └─ Sentry init start → init completion, before app-runner execution
├── Root Widget Attachment [app.start.root_widget_attachment]
│   └─ initial attachToBuildOwner entry → successful return
├── Frame Build [app.start.frame_build] (up to 10)
│   └─ handleBeginFrame entry → successful drawFrame return
├── Frame Rasterization [app.start.frame_raster]
│   └─ first frame's engine raster start → raster finish
└── Native detail spans [app.start.native]

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.load are also unchanged.

💡 Motivation and Context

Make slow startup work attributable to the phase that caused it.

Refs #3838.

💚 How did you test it?

  • 230 focused tests passed after merging the latest v10-branch; formatting and analysis checks passed.
  • Tested startup configurations on a physical Android phone and an iOS simulator, including inspection of envelopes before native handoff.
  • Confirmed example traces reached Sentry and separated attachment, build, and raster workloads.

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPii is enabled
  • I updated the docs if needed
  • All tests passing
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec
  • No breaking changes

🔮 Next steps

Run full CI and review the changed span names and attributes before release.

buenaflor and others added 2 commits September 15, 2026 11:41
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>
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

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


Features

  • (flutter) Revamp app-start frame spans by buenaflor in #4018

🤖 This preview updates automatically when you update the PR.

@codecov

codecov Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.73643% with 11 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v10-branch@fbdb074). Learn more about missing BASE report.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...start/standalone/standalone_app_start_handler.dart 84.84% 10 Missing ⚠️
packages/flutter/lib/src/binding_wrapper.dart 94.73% 1 Missing ⚠️
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           
Flag Coverage Δ
sentry 88.53% <100.00%> (?)
sentry_dio 97.95% <ø> (?)
sentry_drift 93.63% <ø> (?)
sentry_file 64.41% <ø> (?)
sentry_firebase_remote_config 100.00% <ø> (?)
sentry_flutter 92.91% <95.25%> (?)
sentry_grpc 99.11% <ø> (?)
sentry_hive 76.37% <ø> (?)
sentry_isar 73.88% <ø> (?)
sentry_link 22.04% <ø> (?)
sentry_logging 97.01% <ø> (?)
sentry_sqflite 88.88% <ø> (?)
sentry_supabase 97.48% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

buenaflor and others added 11 commits September 15, 2026 17:20
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>
@buenaflor
buenaflor marked this pull request as ready for review September 16, 2026 13:28
@buenaflor
buenaflor requested a review from denrase as a code owner September 16, 2026 13:28
Copilot AI lite review requested due to automatic review settings September 16, 2026 13:28

Copilot AI left a comment

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.

🟡 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

  • SentrySpanOperations is re-exported from the public packages/dart/lib/sentry.dart barrel, 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

  • _minimumEndTimestamp is applied to every finish() call, including the explicit _root.finish(endTimestamp: _finalDeadlineTimestamp) in StaticAppStartTrace._finishAtDeadline. Because recordFirstFrame is 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.initState for MyApp runs when the root element is mounted and built during the first frame; attachToBuildOwner only installs and schedules the root before that lifecycle callback. Consequently this workload will be attributed to Frame Build, not Root 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-on First Frame Render parent (app.start.first_frame_render) spanning first vsync to raster finish, with Vsync Overhead, Frame Build, Raster Handoff, and Frame Rasterization below 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 Startup span. 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 $AppStartTiming subject 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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

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>
Comment thread packages/flutter/MIGRATION.md Resolved
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>

@lucas-zimerman lucas-zimerman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants