In Flutter, how do you measure the time to first frame with flutter run --trace-startup, and what does it report?
answer
- run in profile mode
- the app exits after tracing
- start_up_info.json in build
- built versus rasterized first frame
- measured from engine entry
basics
~20 sRun flutter run --profile --trace-startup: the tool records startup, prints 'Time to first frame', writes start_up_info.json to the build directory and exits. It measures from the engine's entry point to the first frame built and rasterized.
solid answer
~40 s`flutter run --trace-startup` launches the app, records the startup timeline through the VM service, then saves it and exits instead of staying attached; hot reload is off in this mode. Use it with `--profile` so the numbers reflect optimized code. It prints `Time to first frame: Nms.` and writes `start_up_info.json` (plus the raw `start_up_timeline.json`) to the `build` directory, or to `FLUTTER_TEST_OUTPUTS_DIR` if set. The JSON holds `timeToFrameworkInitMicros`, `timeToFirstFrameMicros` (first frame **built**), `timeToFirstFrameRasterizedMicros` (first frame **on screen**) and `timeAfterFrameworkInitMicros`, all measured from the engine's `FlutterEngineMainEnter` event, so native process startup before the engine runs is not included. Inside the app, `WidgetsBinding.instance.firstFrameRasterized` tells you when that moment happened.
code
bash · 6 linesflutter run --profile --trace-startup
# ...
# Time to first frame: 412ms.
# Saved startup trace info in build/start_up_info.json.
cat build/start_up_info.jsongo deeper
Remember the command, flutter run --profile --trace-startup, and that it prints the time to first frame and saves a JSON in build.
Explain each key in start_up_info.json, why profile mode is used, and what the measurement leaves out.
Use timeAfterFrameworkInitMicros and the built-versus-rasterized gap to decide whether your main(), your first build or rendering is the delay.
Combine lab traces with firstFrameRasterized timings from real devices, so startup regressions are caught before users report them.
## What "time to first frame" means When a Flutter app launches, the user sees the platform's launch screen until Flutter paints its **first frame**. Several stages run before that: 1. The native app starts and the Flutter **engine** begins running. 2. The Dart VM starts and your `main()` runs. 3. The framework's **bindings** initialize (`WidgetsFlutterBinding.ensureInitialized()` or `runApp`). 4. The first widget tree is **built**, laid out and painted. 5. The first frame is **rasterized** and appears on screen. Time to first frame is the interval up to stage 4 or 5. It is the startup number Flutter's own performance dashboards track. ## Measuring it with --trace-startup Run: ```bash flutter run --profile --trace-startup ``` What happens: - The tool launches the app, connects to it and records the timeline while it starts. - Hot mode is disabled for this run; once the first frame is recorded, the tool saves the trace and the app session **ends** rather than staying attached. - It prints a line such as `Time to first frame: 412ms.` and `Saved startup trace info in build/start_up_info.json.` - Files go to the `build` directory, or to the directory in the `FLUTTER_TEST_OUTPUTS_DIR` environment variable. Use **profile mode**: debug builds run unoptimized code and exaggerate startup. Release builds cannot be traced this way, because the timeline events it needs are only emitted outside release mode. ## Reading start_up_info.json | Key | Meaning | |---|---| | `engineEnterTimestampMicros` | Timestamp of the engine's `FlutterEngineMainEnter` event: the zero point | | `timeToFrameworkInitMicros` | Engine entry until the framework's bindings start initializing | | `timeToFirstFrameMicros` | Engine entry until the first useful frame was **built** | | `timeToFirstFrameRasterizedMicros` | Engine entry until that frame was **rasterized** (on screen) | | `timeAfterFrameworkInitMicros` | Framework initialization until the first frame was built | Two readings matter most: - A large `timeAfterFrameworkInitMicros` means your own code between binding initialization and the first build, such as awaited setup in `main()`, is the delay. - The gap between built and rasterized is the cost of rendering that first frame. ## What it does not measure - **Anything before the engine starts.** The clock starts at `FlutterEngineMainEnter`, so the OS launching the process and native application setup are outside the number. Measure full cold start with platform tools if you need it. - **Content readiness.** The "first useful frame" can be a loading screen. If the user waits for data after that, measure that separately. ## Reading it from inside the app `WidgetsBinding.instance.firstFrameRasterized` is `true` once the first frame is on screen, and `waitUntilFirstFrameRasterized` is a `Future` that completes at that moment, useful for logging real-world startup from devices in the field.
- Why can't you use flutter run --release --trace-startup to measure startup?The startup trace is read from timeline events such as 'Widgets built first useful frame' and 'Rasterized first useful frame', which the framework only emits outside release mode, and release builds do not expose the VM service the tool records through. Profile mode is optimized like release but keeps both.
- What is the difference between timeToFirstFrameMicros and timeToFirstFrameRasterizedMicros?The first is when the framework finished building the first useful frame; the second is when that frame was rasterized and shown. The gap is the cost of rendering it. The tool keeps the built number as its headline for continuity with older benchmarks and adds the rasterized one as the more accurate figure.
saying these in an interview costs you the question
- --trace-startup keeps the app running with hot reload after it prints the result.
- Debug mode gives realistic startup numbers.
- Time to first frame includes the OS creating the app's process.
- The first frame always means the user's real content is visible.
- The trace file is written next to pubspec.yaml by default.