Profile Dart VM programs from the terminal and get results that are useful to humans and AI agents without opening the DevTools UI.
This package set is for CLI-first profiling:
- run a Dart or Flutter command under the VM profiler
- capture the whole process even when the target code is not instrumented
- mark named regions inside the target program when you want narrower results
- collect CPU call trees and memory summaries for the whole run and each region
- summarize, explain, compare, and inspect stored profile artifacts
- expose the same workflow through a local stdio MCP server for agents
It intentionally does not use the DevTools Flutter or web UI.
Install the CLI once:
dart pub global activate devtools_profiler_cli
devtools-profiler helpWhen testing an unpublished checkout, activate the local CLI package instead:
dart pub global activate --source path packages/devtools_profiler_cli
devtools-profiler helpRun the bundled sample app:
devtools-profiler run \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd packages/devtools_profiler_core/test/fixtures/profiled_app \
-- dart run bin/profiled_app.dartProfile your own Dart file:
devtools-profiler run \
--cwd /path/to/your/dart/app \
bin/main.dartProfile a full Dart command:
devtools-profiler run \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--bottom-up \
--method-table \
--cwd /path/to/your/dart/app \
-- dart run bin/main.dartBare Dart files are expanded to dart run <file>. For full Dart or Flutter
commands, put profiler options before the target command and use -- when the
target command has its own options. Dart launches are held at isolate exit long
enough for final CPU and memory snapshots, so short scripts can still produce a
whole-session profile.
Profile the bundled terminal UI fixture with direct stdio inheritance:
devtools-profiler run \
--terminal \
--cwd packages/devtools_profiler_core/test/fixtures/profiled_app \
-- dart run bin/artisanal_widget_app.dartUse --terminal for TUI programs that need stdin, raw mode, mouse tracking, or
alternate-screen rendering. Terminal mode cannot be combined with --json
because the target owns stdout and stderr while it runs. When the CLI receives
Ctrl+C or SIGTERM, it finalizes the diagnostics captured so far and then prints
the normal session summary.
Profile a Flutter test run:
devtools-profiler run \
--json \
--duration 15s \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd /path/to/flutter/app \
-- flutter test test/widget_test.dartProfile a Flutter app run:
devtools-profiler run \
--json \
--duration 15s \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd /path/to/flutter/app \
-- flutter run -d linuxFlutter profiling supports VM-service targets exposed by flutter run and
flutter test. Flutter release mode, browser/web targets, and AOT builds do not
expose the VM service needed by this profiler.
--duration starts after the VM service is available, so Flutter build time is
not counted as profiling time. Flutter commands get a longer default VM-service
startup wait than Dart commands. Use --vm-service-timeout 5m if the first
build takes longer.
Attach to an already-running Dart or Flutter app when you want to avoid rebuilding or relaunching for every profiling window:
flutter run -d linux -t lib/main_relic_breach.dart --host-vmservice-port=0Copy the VM service URI printed by Flutter, then run:
devtools-profiler attach \
--duration 15s \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd /path/to/flutter/app \
http://127.0.0.1:8181/abcd/Attach mode clears the VM's existing CPU samples, profiles a fixed window from the existing VM service, and leaves the app running. It captures the whole-session profile view. Explicit region markers require launch mode because the target must be started with the profiler's DTD session configuration.
The run writes a session directory under:
.dart_tool/devtools_profiler/sessions/<session-id>/
That session contains the whole-run profile plus any explicit regions emitted by the target program.
Region names such as checkout or startup are user-visible labels. Region
selectors use generated regionId values from the session output. The CLI
prints region ids in summaries, and the MCP server exposes them through
profile_list_regions.
Use this profiler when you want to answer questions like:
- Which methods are taking the most CPU in this Dart script?
- What changed between yesterday's run and today's run?
- Which code path became slower in a marked region such as
startup,checkout,parse-file, orrender-report? - What does the call tree look like below a hot function?
- Which callers lead to this expensive leaf method?
- Did memory usage change across a region?
- Can an AI agent run a script, profile it, and inspect the result without a browser?
You do not need to mark regions to get value. If the target program has no regions, the profiler still captures the full session. If the target program does mark regions, the session includes both the full view and each region view.
Add the helper package to the target app:
dart pub add devtools_region_profilerWhen testing an unpublished checkout, use a path dependency to the local
packages/devtools_region_profiler directory instead.
Wrap the code you want to measure:
import 'package:devtools_region_profiler/devtools_region_profiler.dart';
Future<void> main() async {
await profileRegion(
'startup',
attributes: {'phase': 'bootstrap'},
() async {
await loadConfiguration();
await warmUpCache();
await startServer();
},
);
}A region includes the work done by functions it calls. If startup calls
warmUpCache, and warmUpCache calls readIndex, sampled stack frames for all
of those methods can appear in the region's call tree.
Use the manual handle form when the region cannot be expressed as a single closure:
import 'package:devtools_region_profiler/devtools_region_profiler.dart';
Future<void> handleCheckout() async {
final region = await startProfileRegion(
'checkout',
attributes: {'phase': 'pricing'},
options: const ProfileRegionOptions(
isolateScope: ProfileIsolateScope.current,
),
);
try {
await validateCart();
await priceCart();
await persistOrder();
} finally {
await region.stop();
}
}Regions can be nested:
await profileRegion('request', () async {
await profileRegion('load-user', () async {
await loadUser();
});
await profileRegion('render-response', () async {
await renderResponse();
});
});Default region behavior:
- CPU and memory are captured.
- Only the current isolate is captured.
- Nested regions inherit the active parent region.
- Calling the helper outside a profiler-launched run throws
ProfileRegionConfigurationException.
Use all-isolate capture for regions that intentionally fan out work:
await profileRegion(
'parallel-build',
options: const ProfileRegionOptions(
isolateScope: ProfileIsolateScope.all,
),
() async {
await buildInWorkerIsolates();
},
);Human output is designed for terminal scanning. JSON output is designed for automation and AI agents.
Use human output while exploring:
devtools-profiler summarize \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
/path/to/sessionUse JSON output when another tool or agent will consume the result:
devtools-profiler summarize \
--json \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--bottom-up \
--method-table \
/path/to/sessionJSON responses include a cliCommand field for the command that can reproduce
the same analysis selection.
Important result sections:
overallProfile: the whole run from process start to process exit.regions: profiles for named regions emitted by the target app.topSelfFrames: where samples stopped. This is the best first view for exclusive CPU cost.topTotalFrames: methods that appeared in sampled stacks. This is useful for finding broad inclusive influence, but totals are not additive.callTree: top-down view from roots to callees. Use this to see what a region or whole session did.bottomUpTree: leaf-to-caller view. Use this when you know the hot leaf and need to find the caller chain that led there.methodTable: DevTools-style method context with callers and callees.memory: heap and allocation summary when memory capture was available.classes: memory class rows frominspect-classes.regressionsandtrends: comparison output for reasoning across sessions.
Filtering options keep the output readable:
--hide-sdk
--hide-runtime-helpers
--include-package my_app
--exclude-package testUse a full, unfiltered view when you need every frame:
--frame-limit 0 --tree-depth 0 --tree-children 0 --method-limit 0Use --full-locations when exact source paths matter. Leave it off when you
want shorter terminal output.
devtools-profiler run \
--json \
--duration 15s \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd /path/to/app \
-- dart run bin/script.dart --input data.jsonThis works even if the script has no region instrumentation.
devtools-profiler run \
--json \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--method-table \
--cwd /path/to/flutter/app \
-- flutter test test/widget_test.dartFor flutter test, the profiler enables the VM service automatically. For
flutter run, the profiler requests a random host VM-service port. The
--duration timer starts only after the backend attaches to that VM service.
If the app takes longer to build or start, pass a larger value such as
--vm-service-timeout 5m.
The backend also injects profiler session values through --dart-define so
devtools_region_profiler can mark regions in Flutter targets that can connect
back to the local DTD server.
Device and mobile runs can still provide whole-session CPU data when Flutter prints a VM-service URI. Region markers require the app process to reach the profiler's local DTD URI, which is straightforward for host-side Flutter tests and desktop runs but may require additional networking for device runs.
Use attach mode when the app is already running and you want repeated profiling windows without paying Flutter build/startup cost each time.
Start the app once:
flutter run -d linux -t lib/main_relic_breach.dart --host-vmservice-port=0For a lower-overhead runtime closer to deployed Flutter behavior, use profile mode instead of debug mode when your target supports it:
flutter run --profile -d linux -t lib/main_relic_breach.dart \
--host-vmservice-port=0Then copy the VM service URI from Flutter's output and attach:
devtools-profiler attach \
--json \
--duration 15s \
--hide-sdk \
--hide-runtime-helpers \
--call-tree \
--bottom-up \
--method-table \
--cwd /path/to/flutter/app \
http://127.0.0.1:8181/abcd/Repeat the attach command for another 15-second window. Each attach clears the
VM's existing CPU sample buffer before collecting the new window. The profiler
does not stop the Flutter app, so this is the preferred workflow for exploratory
profiling of long-running games, desktop apps, servers, and demos.
Run the app normally through the profiler:
devtools-profiler run \
--json \
--call-tree \
--bottom-up \
--method-table \
--cwd /path/to/app \
-- dart run bin/server.dartThe session contains overall plus each emitted region. Commands that analyze a
single profile accept --profile-id overall or a generated region id from the
session output.
devtools-profiler explain \
--json \
--profile-id overall \
/path/to/sessionUse a generated region id instead of overall to explain only that region:
devtools-profiler explain \
--json \
--profile-id <checkout-region-id> \
/path/to/sessionexplain returns prioritized hotspot insights, representative top-down paths,
representative bottom-up paths, and method context for likely problem areas.
Find candidate methods:
devtools-profiler search-methods \
--json \
--query Checkout \
--sort total \
/path/to/sessionInspect one method:
devtools-profiler inspect \
--json \
--method CheckoutService.priceCart \
--profile-id <checkout-region-id> \
/path/to/sessionInspection shows self cost, inclusive cost, callers, callees, and representative paths.
devtools-profiler inspect-classes \
--json \
--class Cart \
--min-live-bytes 1048576 \
/path/to/sessioninspect-classes re-reads the stored memory artifact and reports retained class
rows, live instances, and allocation deltas. Use --limit 0 for an unlimited
class list.
devtools-profiler compare \
--json \
--method-table \
/path/to/baseline-session \
/path/to/current-sessionSelect specific profiles inside session directories:
--baseline-profile-id overall --current-profile-id overallor compare specific regions by copying their generated ids from each session:
--baseline-profile-id <baseline-checkout-region-id> \
--current-profile-id <current-checkout-region-id>devtools-profiler compare-method \
--json \
--method CheckoutService.priceCart \
--baseline-profile-id <baseline-checkout-region-id> \
--current-profile-id <current-checkout-region-id> \
/path/to/baseline-session \
/path/to/current-sessionPass sessions oldest to newest:
devtools-profiler trends \
--json \
/path/to/session-1 \
/path/to/session-2 \
/path/to/session-3Trend output includes per-run points, adjacent comparisons, first-to-last comparison, recurring regressions, and prioritized overall regressions.
Run:
devtools-profiler <command>Show built-in help:
devtools-profiler helpCommands:
run [--] <dart-file|command...>launches and profiles a Dart file, Dart command, or Flutter command.attach <vm-service-uri>profiles an already-running VM service for a fixed--duration.summarize <path>summarizes a session directory or profile artifact.explain <path>explains likely hotspots in one selected profile.compare <baseline> <current>compares two profiles or sessions.trends <path>...analyzes a sequence of profiles or sessions.inspect <path>inspects one method in one profile.inspect-classes <path>inspects memory classes in one profile.search-methods <path>searches methods in one profile.compare-method <baseline> <current>compares one method across two profiles.mcpstarts the local stdio MCP server.
Common presentation flags:
--jsonemits structured JSON instead of human output.--call-treeincludes a top-down call tree.--expandis an alias for--call-tree.--bottom-upincludes a bottom-up call tree.--method-tableincludes a DevTools-style method table.--hide-sdkhides Dart and Flutter SDK frames.--hide-runtime-helpershides common profiler/runtime helper packages.--include-package <prefix>keeps only matching package prefixes. May be repeated.--exclude-package <prefix>excludes matching package prefixes. May be repeated.--full-locationsshows full file locations instead of shortened labels.--frame-limit <n>controls self/total rows.0means unlimited.--tree-depth <n>controls call-tree depth.0means unlimited.--tree-children <n>controls children per tree node.0means unlimited.--method-limit <n>controls method rows and relations.0means unlimited.--min-live-bytes <n>filters memory class rows forcompareandinspect-classes.--memory-class-limit <n>controls compared memory class rows forcompare.0means unlimited.
run options:
--cwd <dir>sets the working directory for the launched process.--artifact-dir <dir>sets an explicit artifact output directory.--duration <duration>stops the launched process after profiling for that duration. Examples:15s,2m,500ms.--vm-service-timeout <duration>controls how long to wait for the launched process to expose a VM service URI before profiling starts. Examples:3m,300s.--forward-outputforwards child stdout/stderr. Defaults totrue.--terminalgives the launched process direct terminal access for TUI and alternate-screen apps. It cannot be combined with--json.
attach options:
--duration <duration>is required and controls the attached profiling window. Examples:15s,2m,500ms.--cwd <dir>sets the working directory used for relative artifact display and the default.dart_tool/devtools_profilerlocation.--artifact-dir <dir>sets an explicit artifact output directory.
Path arguments accepted by read/analyze commands:
- a session directory
- a region
summary.json - a raw
cpu_profile.json - a raw
memory_profile.jsonfor memory-class inspection
Start the server:
devtools-profiler mcpThe MCP server uses local stdio transport. A client configuration usually points at the same command:
{
"command": "devtools-profiler",
"args": ["mcp"]
}Tools by workflow:
- Capture:
profile_run,profile_attach. - Navigate stored runs:
profile_list_sessions,profile_latest_session,profile_get_session,profile_list_regions,profile_get_region. - Read artifacts:
profile_summarize,profile_read_artifact. - Explain and drill down:
profile_explain_hotspots,profile_search_methods,profile_inspect_method,profile_inspect_classes. - Compare:
profile_compare,profile_compare_method,profile_find_regressions,profile_analyze_trends.
Useful agent pattern:
- Call
profile_runwithincludeCallTree,includeBottomUpTree, andincludeMethodTable, or callprofile_attachwith the same presentation flags when the target app is already running. - Call
profile_explain_hotspotsforoverall. - If regions exist, call
profile_explain_hotspotsfor the hottest region. - Use
profile_search_methodsandprofile_inspect_methodfor named functions mentioned by the explanation. - Use
profile_inspect_classeswhen memory summaries show retained growth. - Use
profile_compareorprofile_find_regressionsafter a code change.
Most read-only tools accept either direct paths or stored-session selectors:
- direct path selectors:
path,baselinePath,currentPath - session selectors:
rootDirectory,sessionsDirectory,sessionId,sessionPath
Session ids also support latest and previous.
For trends, profile_analyze_trends accepts explicit paths, explicit
sessionIds, or a session directory plus limit. When limit is used, the
server chooses the newest N sessions and analyzes them oldest-to-newest.
Each run or attach creates a session directory:
.dart_tool/devtools_profiler/sessions/<session-id>/
session.json
overall/
summary.json
cpu_profile.json
memory_profile.json
regions/
<region-id>/
summary.json
cpu_profile.json
memory_profile.json
Important files:
session.jsoncontains run metadata, region metadata, warnings, and artifact paths.summary.jsoncontains one profile summary with top frames and optional memory summary.cpu_profile.jsoncontains raw VM CPU samples used to rebuild trees and method tables.memory_profile.jsoncontains memory snapshot and diff data when memory capture was available.
This repository contains:
packages/devtools_profiler_cli: CLI executable and local stdio MCP server.- Region profiler: helper package used by target programs to mark profiling regions.
- Profiler core: pure-Dart backend for launch, VM service attachment, DTD coordination, region handling, artifacts, comparison, hotspot explanation, and trend analysis.
- Profiler protocol: shared protocol models used between the helper and backend.
The profiler uses the hosted devtools_shared package for shared VM and memory
models. It does not depend on packages/devtools_app,
packages/devtools_app_shared, or other Flutter/web UI packages.
- Attach mode captures a fixed whole-session VM-service window from an existing process, but explicit region markers normally require launch mode.
- Launch mode supports bare Dart files, Dart VM commands, and supported Flutter
commands. Put profiler options before the target, and use
--when the target command has its own options. dart compile ...targets and Flutter release/AOT targets are not supported.- Flutter support is limited to VM-service targets from
flutter runandflutter test; browser/web profiling is not supported. - CPU and memory capture are implemented. The protocol reserves a
timelinecapture kind, but timeline capture is not implemented. - MCP is local stdio only.
Run these from profiler/ when changing the profiler packages:
dart analyze .
dart test packages/devtools_profiler_core
dart test packages/devtools_profiler_cli
dart test packages/devtools_region_profiler
dart test packages/devtools_profiler_protocol