Skip to content

Output Formats

albertoodev edited this page Jul 28, 2026 · 8 revisions

Output Formats

Every SPM command emits JSON Lines (JSONL): one self-contained JSON object per line. This page documents the schemas produced by analyze (static) and the instrumented app under run (runtime). Field names and types here are taken directly from the toJson methods in lib/src/features/*/data/models/. For the meaning of each analysis metric, see Extracted Features.


Static analysis JSONL (analyze)

One JSON object per line, one line per rebuild scope — a State subclass, a ConsumerWidget/HookConsumerWidget, or the builder callback of a state-management widget (BlocBuilder, Consumer, Obx, …). The analyze output contains exactly these 18 fields (AnalysisResultModel.toJson):

{
  "instanceId": "42",
  "filePath": "lib/screens/shopping_cart_screen.dart",
  "scopeName": "_ShoppingCartScreenState",
  "scopeType": "State",
  "treeNonConstWidgetCount": 24,
  "treeMaxWidgetNestingDepth": 7,
  "treeListRenderingStrategy": 2,
  "rootBuildReturnsConstWidget": 0,
  "treeConstWidgetCount": 2,
  "helperReferenceCount": 2,
  "usesLayoutDependentBuilder": 1,
  "treeCyclomaticComplexity": 5,
  "treeIterationCount": 1,
  "treeMaxIterationNestingDepth": 1,
  "iterationWidgetCount": 3,
  "valueObjectAllocCount": 4,
  "helperWidgetCount": 3,
  "helperMaxWidgetNestingDepth": 2
}

Field reference

Field Type Description
instanceId string ID for this scope: hash of the root-relative path plus the scope name (and, for non-State scopes, its type and occurrence index). Used to join with runtime profiler data. It is not unique across transplanted targets that share one file/class, so the benchmark pipeline keys by sample_id instead.
filePath string Source path relative to the analyzed project root.
scopeName string Class name of the scope, or <Widget>_builder for a builder callback (e.g. BlocBuilder_builder). (Named stateClassName before rebuild-scope support.)
scopeType string Kind of rebuild scope: State, ConsumerWidget, or the builder widget name (BlocBuilder, BlocSelector, BlocConsumer, Consumer, Selector, Obx, GetX, GetBuilder, Observer).
treeNonConstWidgetCount int Non-const widget instantiations across reachable build bodies; excludes const boundaries and helper-body widgets.
treeMaxWidgetNestingDepth int Maximum widget depth composed across reachable custom-widget build bodies.
treeListRenderingStrategy 0/1/2 Worst list-rendering form across builds and helpers: 0 none, 1 lazy (.builder/.separated/.custom, slivers), 2 eager O(N) (concrete children: on a scroll list, or runtime-length Column/Row/Wrap/Flex children). (Replaced the buildUsesListViewBuilder boolean on 2026-07-20.)
rootBuildReturnsConstWidget 0/1 Whether the root State.build directly returns a const widget.
treeConstWidgetCount int Const widget boundaries across reachable build bodies; helper-body const widgets belong to helperWidgetCount. (Named constConstructorRatio in JSONL produced before 2026-07-18.)
helperReferenceCount int Widget-returning helper reference sites in reachable build bodies: invocations, tear-offs, and explicit getters.
usesLayoutDependentBuilder 0/1 Whether builds or helpers use LayoutBuilder, CustomMultiChildLayout, or Flow.
treeCyclomaticComplexity int Complexity summed across reachable build bodies, plus helper-body decision points without extra helper base points.
treeIterationCount int Loops, collection-for elements, and linear collection operations across build and helper bodies.
treeMaxIterationNestingDepth int Maximum lexical iteration nesting within any analyzed build or helper body.
iterationWidgetCount int Non-const widgets built in per-element scopes across builds and helpers: loops, collection-op callbacks, and lazy builders.
valueObjectAllocCount int All non-const, non-widget constructor allocations across build and helper bodies.
helperWidgetCount int Widget instantiations in reachable helper bodies, including const widgets.
helperMaxWidgetNestingDepth int Maximum widget nesting within any individual reachable helper body.

0/1 fields are Dart bools serialized as 1/0 by toJson. See Extracted Features for the precise definition of each metric.

Key migration (2026-07-20 rename)

Feature keys were renamed so every name states its scope: tree* = aggregated over the whole static call tree, root* = root build() only, helper* = helper bodies only. Older JSONL files can be normalized with this map:

Old key (pre-2026-07-20) Current key
buildWidgetInstanceCount treeNonConstWidgetCount
buildMaxWidgetNestingDepth treeMaxWidgetNestingDepth
buildUsesListViewBuilder (bool, pre-07-20) / buildListRenderingStrategy treeListRenderingStrategy
buildReturnsConstWidget rootBuildReturnsConstWidget
constConstructorRatio (pre-07-18) / constWidgetCount treeConstWidgetCount
buildHelperMethodCount helperReferenceCount
buildCyclomaticComplexity treeCyclomaticComplexity
buildIterationCount treeIterationCount
buildMaxIterationNestingDepth treeMaxIterationNestingDepth
helperMethodWidgetCount helperWidgetCount
helperMethodMaxNestingDepth helperMaxWidgetNestingDepth

usesLayoutDependentBuilder, iterationWidgetCount, valueObjectAllocCount, and the identity keys (instanceId, filePath, stateClassName — since renamed to scopeName) are unchanged. The legacy boolean buildUsesListViewBuilder maps onto the none/lazy levels of the ordinal only; it cannot express eager (2).

Rebuild-scope rows

analyze emits one row per rebuild scope, so a file can contribute several overlapping rows: a State row counts the widgets built inside a nested BlocBuilder callback and the callback gets its own row. That is deliberate — a parent rebuild re-runs the callback, while the callback row measures the path its package can rebuild on its own.

Use --scope-types to narrow the output (--scope-types=State reproduces the pre-rebuild-scope rows). inject only instruments State subclasses and skips rows of any other scopeType, so a full-scope JSONL can be passed to it unchanged.


Runtime profiler JSONL (run / instrumented app)

Two event types are emitted, distinguished by the event field. Which one you get depends on the Flutter build mode (see inject): --profileperformance_metric, --debugdataflow-metric. Every event carries a timestamp (ISO-8601) and the instanceId that joins back to the static-analysis record.

Performance event, emitted under --profile (PerformanceMetricsModel.toJson):

{
  "timestamp": "2026-02-20T10:15:30.123Z",
  "event": "performance_metric",
  "instanceId": "42",
  "buildSpan": 4123
}
Field Type Description
timestamp string ISO-8601 timestamp of the event.
event string Always "performance_metric".
instanceId string The instrumented State instance.
buildSpan int Build duration of the matched rebuild frame, in microseconds (0 on the 5-second timeout fallback).

Dataflow event, emitted under --debug (DataflowMetricsModel.toJson):

{
  "timestamp": "2026-02-20T10:15:30.456Z",
  "event": "dataflow-metric",
  "instanceId": "42",
  "taintedRebuildCount": 5,
  "totalWidgetCount": 42,
  "maxNestingDepth": 7,
  "taintedRatio": 0.119,
  "opacityRebuildCount": 0,
  "shaderMaskRebuildCount": 0,
  "clipRRectRebuildCount": 1,
  "clipOvalRebuildCount": 0,
  "clipPathRebuildCount": 0,
  "backdropFilterRebuildCount": 0
}
Field Type Description
timestamp string ISO-8601 timestamp of the event.
event string Always "dataflow-metric".
instanceId string The instrumented State instance.
taintedRebuildCount int Widgets that actually rebuilt as a result of the setState.
totalWidgetCount int Total widgets in the analyzed subtree.
maxNestingDepth int Maximum nesting depth of the analyzed subtree.
taintedRatio double taintedRebuildCount / totalWidgetCount.
opacityRebuildCount int Rebuilt Opacity widgets.
shaderMaskRebuildCount int Rebuilt ShaderMask widgets.
clipRRectRebuildCount int Rebuilt ClipRRect widgets.
clipOvalRebuildCount int Rebuilt ClipOval widgets.
clipPathRebuildCount int Rebuilt ClipPath widgets.
backdropFilterRebuildCount int Rebuilt BackdropFilter widgets.

Clone this wiki locally