vm_snapshot_analysis

0.7.6

Utilities for analysing AOT snapshot size.

Download archive

Readme

pub package package publisher

This package provides libraries and a utility for analysing the size and contents of Dart VM AOT snapshots based on the output of --print-instructions-sizes-to, --write-v8-snapshot-profile-to and --trace-precompiler-to VM flags.

AOT Snapshot Basics

Dart VM AOT snapshot is simply a serialized representation of the Dart VM heap graph. It consists of two parts: data (e.g. strings, const instances, objects representing classes, libraries, functions and runtime metadata) and executable code (machine code generated from Dart sources). Some nodes in this graph have clean and direct relationship to the original program (e.g. objects representing libraries, classes, functions), while other nodes don't. Bitwise equivalent objects can be deduplicated and shared (e.g. two functions with the same body will end up using the same machine code object). This makes impossible to attribute of every single byte from the snapshot to a particular place in the program with a 100% accuracy.

  • --print-instructions-sizes-to attributes executable code from the snapshot to a particular Dart function (or internal stub) from which this code originated (ignoring deduplication). Executable code usually constitutes around half of the snapshot, those this varies depending on the application.
  • --write-v8-snapshot-profile-to is a graph representation of the snapshot, it attributes bytes written into a snapshot to a node in the heap graph. This format covers both data and code sections of the snapshot.
  • --trace-precompiler-to gives information about dependencies between compiled functions, allowing to determine why certain function was pulled into the snapshot.

Passing flags to the AOT compiler

In both Flutter and dart compile exe / dart compile aot-snapshot you can use --extra-gen-snapshot-options to pass flags to the AOT compiler:

$ flutter build aot --release --extra-gen-snapshot-options=--write-v8-snapshot-profile-to=profile.json

$ dart compile exe --extra-gen-snapshot-options=--write-v8-snapshot-profile-to=profile.json -o binary input.dart

Similarly with --print-instructions-sizes-to.

If you are working on the Dart SDK you can use the pkg/vm/tool/precompiler2 script, in which case you can just pass these flags directly:

$ pkg/vm/tool/precompiler2 --write-v8-snapshot-profile-to=profile.json input.dart binary

CLI

The command line interface to the tools in this package is provided by a single entry point bin/analyse.dart. It consumes output of --print-instructions-sizes-to and --write-v8-snapshot-profile-to flags and presents it in different human readable ways.

This script can be installed globally as snapshot_analysis using

$ pub global activate vm_snapshot_analysis

snapshot_analysis supports the following subcommands:

summary

$ snapshot_analysis summary [-b granularity] [-w filter] <input-profile.json>

This command shows breakdown of snapshot bytes at the given granularity (e.g. method, class, library or package), filtered by the given substring filter.

For example, here is a output showing how many bytes from a snapshot can be attributed to classes in the dart:core library:

$ pkg/vm/bin/snapshot_analysis.dart summary -b class -w dart:core profile.json
+-----------+------------------------+--------------+---------+----------+
| Library   | Class                  | Size (Bytes) | Percent | Of total |
+-----------+------------------------+--------------+---------+----------+
| dart:core | _Uri                   |        43563 |  15.53% |    5.70% |
| dart:core | _StringBase            |        28831 |  10.28% |    3.77% |
| dart:core | ::                     |        27559 |   9.83% |    3.60% |
| @other    |                        |        25467 |   9.08% |    3.33% |
| dart:core | Uri                    |        14936 |   5.33% |    1.95% |
| dart:core | int                    |        12276 |   4.38% |    1.61% |
| dart:core | NoSuchMethodError      |        12222 |   4.36% |    1.60% |
...

Here objects which can be attributed to _Uri take 5.7% of the snapshot, at the same time objects which can be attributed to dart:core library but not to any specific class within this library take 3.33% of the snapshot.

This command also supports estimating cumulative impact of a library or a package together with its dependencies - which can be computed from a precompiler trace (generated by adding --trace-precompiler-to=path/to/trace.json to --extra-gen-snapshot-options=).

$ snapshot_analysis summary [-b granularity] [-w filter] [-s dep-tree-start-depth] [-d dep-tree-display-depth] --precompiler-trace=<input-trace.json> <input-profile.json>

For example:

$ snapshot_analysis summary -b package /tmp/profile.json
+-----------------------------+--------------+---------+
| Package                     | Size (Bytes) | Percent |
+-----------------------------+--------------+---------+
| package:compiler            |      5369933 |  38.93% |
| package:front_end           |      2644942 |  19.18% |
| package:kernel              |      1443568 |  10.47% |
| package:_fe_analyzer_shared |       944555 |   6.85% |
...
$ snapshot_analysis summary -b package -s 1 -d 3 --precompiler-trace=/tmp/trace.json /tmp/profile.json
+------------------------------+--------------+---------+
| Package                      | Size (Bytes) | Percent |
+------------------------------+--------------+---------+
| package:compiler (+ 8 deps)  |      5762761 |  41.78% |
| package:front_end (+ 1 deps) |      2708981 |  19.64% |
| package:kernel               |      1443568 |  10.47% |
| package:_fe_analyzer_shared  |       944555 |   6.85% |
...
Dependency trees:

package:compiler (total 5762761 bytes)
├── package:js_ast (total 242490 bytes)
├── package:dart2js_info (total 101280 bytes)
├── package:crypto (total 27434 bytes)
│   ├── package:typed_data (total 11850 bytes)
│   └── package:convert (total 5185 bytes)
├── package:collection (total 15182 bytes)
├── package:_js_interop_checks (total 4627 bytes)
└── package:js_runtime (total 1815 bytes)

package:front_end (total 2708981 bytes)
└── package:package_config (total 64039 bytes)

compare

$ snapshot_analysis compare [-b granularity] <old.json> <new.json>

This command shows comparison between two size profiles, allowing to understand changes to which part of the program contributed most to the change in the overall snapshot size.

$ pkg/vm/bin/snapshot_analysis.dart compare -b class old.json new.json
+------------------------+--------------------------+--------------+---------+
| Library                | Class                    | Diff (Bytes) | Percent |
+------------------------+--------------------------+--------------+---------+
| dart:core              | _SimpleUri               |        11519 |  22.34% |
| dart:core              | _Uri                     |         6563 |  12.73% |
| dart:io                | _RandomAccessFile        |         5337 |  10.35% |
| @other                 |                          |         4009 |   7.78% |
...

In this example 11519 more bytes can be attributed to _SimpleUri class in new.json compared to old.json.

treemap

$ snapshot_analysis treemap [--format <format>] <input.json> <output-dir>
$ google-chrome <output-dir>/index.html

This command generates treemap representation of the information from the profile input.json and stores it in output-dir directory. Treemap can later be viewed by opening <output-dir>/index.html in the browser of your choice.

--format flag allows to control granularity of the output when input.json is a V8 snapshot profile, available options are:

  • collapsed essentially renders ProgramInfo as a treemap, individual snapshot nodes are ignored.
  • simplified same as collapsed, but also folds size information from nested functions into outermost function (e.g. top level function or a method) producing easy to consume output.
  • data-and-code collapses snapshot nodes based on whether they represent data or executable code.
  • object-type (default) collapses snapshot nodes based on their type only.

explain

explain dynamic-calls

$ snapshot_analysis explain dynamic-calls <profile.json> <trace.json>

This command generates a report listing dynamically dispatched selectors and their approximate impact on the code size.

snapshot_analysis explain dynamic-calls /tmp/profile.json /tmp/trace.json
+------------------------------+--------------+---------+----------+
| Selector                     | Size (Bytes) | Percent | Of total |
+------------------------------+--------------+---------+----------+
| set:requestHeader            |        10054 |  28.00% |    0.03% |
| get:scale                    |         3630 |  10.11% |    0.01% |
...
Dynamic call to set:requestHeader (retaining ~10054 bytes) occurs in:
    package:my-super-app/src/injector.dart::Injector.handle{body}

Dynamic call to get:scale (retaining ~3630 bytes) occurs in:
    package:some-dependency/src/image.dart::Image.==

API

This package can also be used as a building block for other packages which want to analyse VM AOT snapshots.

  • package:vm_snapshot_analysis/instruction_sizes.dart provides helpers to read output of --print-instructions-sizes-to=...
  • package:vm_snapshot_analysis/v8_profile.dart provides helpers to read output of --write-v8-snapshot-profile-to=...

Both formats can be converted into a ProgramInfo structure which attempts to breakdown snapshot size into hierarchical representation of the program structure which can be understood by a Dart developer, attributing bytes to packages, libraries, classes and functions.

  • package:vm_snapshot_analysis/utils.dart contains helper method loadProgramInfo which automatically detects format of the input JSON file and creates ProgramInfo in an appropriate way, allowing to write code which works in the same way with both formats.

Precompiler Trace Format (--write-precompiler-trace-to=...)

AOT compiler can produce a JSON file containing information about compiled functions and dependencies between them. This file has the following structure:

{
    "trace": traceArray,
    "entities": entitiesArray,
    "strings": stringsArray,
}
  • stringsArray is an array of strings referenced by other parts of the trace by their index in this array.

  • entitiesArray is an flattened array of entities:

    • "C", <library-uri-idx>, <class-name-idx>, 0 - class record;
    • "V", <class-idx>, <name-idx>, 0 - static field record;
    • "F"|"S", <class-idx>, <name-idx>, <selector-id> - function record (F for dynamic functions and S for static functions);

    Note that all records in this array occupy the same amount of elements (4) to make random access by index possible.

  • traceArray is an flattened array of precompilation events:

    • "R" - root event (always the first element)
    • "E" - end event (always the last element)
    • "C", <function-idx> - function compilation event

    Root and function compilation events can additionally be followed by a sequence of references which enumerate outgoing dependencies discovered by the AOT compiler:

    • <entity-idx> - a reference to a function or a static field;
    • "S", <selector-idx> - a dynamic call with the given selector;
    • "T", <selector-id> - dispatch table call with the given selector id;

Flattened array is an array of records formed by consecutive elements: [R0_0, R0_1, R0_2, R1_0, R1_1, R1_2, ...] here R0_* is the first record and R1_* is the second record and so on.

Features and bugs

Please file feature requests and bugs at the issue tracker.

Changelog

Changelog

0.7.6

  • Allow the old 'patched_class_' field for PatchClass until we change the SDK lower bound for this package to a version that does not have it.

0.7.5

  • Fix for flutter/flutter#130009 where an attempt to lookup the owner info node for type testing stubs would cause a null check error.

0.7.4

  • Fix for flutter/flutter#128402 where an attempt to lookup the owner info node for type testing stubs would cause a null check error.

0.7.3-dev

  • Update the readme to document the current way to generate Dart AOT snapshots.
  • Spelling corrections in dartdoc comments.

0.7.2

  • Upgrade to package:lints 2.0.
  • Populate the pubspec repository field.

0.7.1

  • Make CallGraphNode.dominator nullable.

0.7.0

  • Migrate to null-safety.

0.6.0

  • Update to latest args, path, meta dependency.

0.5.6

  • Fix for flutter/flutter#76313 causing issues with profiles containing WSRs serialized as smi-s instead of actual WSR objects.=

0.5.5

  • Add deps-display-depth (-d) flag for summary command to make the display depth of outputted dependency trees configurable.
  • Rename deps-collapse-depth (formerly -d) flag for summary command to deps-start-depth (now -s).
  • Add generateCallGraphWithDominators method that generates a CallGraph object from precompiler trace.

0.5.4

  • Fix bug causing name clash for Type class.

0.5.3

  • Add compareProgramInfo that takes in two program info objects and outputs a Map object containing the diff data.

0.5.2

  • Add support for package paths that look like package:foo.bar.baz/src/foobar.dart
  • Move commands back to lib.

0.5.0+1

  • Fix broken package by moving non-executable file out of bin/ directory.

0.5.0

  • Remove dart:io dependency from package lib, and move commands to bin.
  • Replace loadProgramInfo util method with loadProgramInfoFromJson, which expects an Object parameter instead of a File parameter.
  • buildComparisonTreemap now expects two Object parameters for oldJson and newJson instead of two File parameters.
  • compare command now prints difference breakdown by node type when this information is available.

0.4.0

  • Add buildComparisonTreemap for constructing treemap representing the diff between two size profiles.
  • Implemented support for extracting call graph information from the AOT compiler trace (--trace-precompiler-to flag), see precompiler_trace.dart.
  • New command explain dynamic-calls which estimates the impact of different dynamic calls on the resulting AOT snapshot size using information from the size dump (e.g. V8 snapshot profile) and AOT compiler trace.
  • summary command can now use information from the AOT compiler trace to group packages/libraries together with their dependencies to given more precise estimate of how much a specific package/library brings into the snapshot.

0.3.0

  • Extract treemap construction code into a separate library, to make it reusable.
  • Add ability to collapse leaf nodes in a treemap created from V8 snapshot profile. This behavior is programmatically controlled by TreemapFormat format parameter and from CLI via --format flag. The following options are available
    • collapsed essentially renders ProgramInfo as a treemap, individual snapshot nodes are ignored.
    • simplified same as collapsed, but also folds size information from nested functions into outermost function (e.g. top level function or a method) producing easy to consume output.
    • data-and-code collapses snapshot nodes based on whether they represent data or executable code.
    • object-type (default) collapses snapshot nodes based on their type only.
  • When computing ProgramInfo from a V8 snapshot profile no longer create ProgramInfoNode for Code nodes which are owned by a function - instead directly attribute the Code node itself and all retained nodes into ProgramInfoNode for the function itself. For stubs (including allocation stubs) create an artificial functionNode instead of using NodeType.other. The only remaining use of NodeType.other is for fields.

0.2.0

  • Update CLI help message to avoid referring to a snapshot created by pub as the name of the script.
  • Fix owner computation code for V8 profiles: the size of a snapshot node which corresponds to a ProgramInfoNode should be attributed to that ProgramInfoNode and not to its parent. For example Function node corresponds to ProgramInfoNode of type functionNode, previously the size of Function node would be attributed to the parent of this ProgramInfoNode, but it should be attributed to the node itself.
  • Update README.md to include more information on how to pass flags to Dart AOT compiler.
  • Add ProgramInfoNode.size documentation to clarify the meaning of the member.

0.1.0

  • Initial release

Installing

export PUB_HOSTED_URL=https://pub.mirror.p-hatan.com
flutter pub add vm_snapshot_analysis
pubspec.yaml
name: vm_snapshot_analysis
version: 0.7.6
description: Utilities for analysing AOT snapshot size.
repository: https://github.com/dart-lang/sdk/tree/main/pkg/vm_snapshot_analysis

environment:
  sdk: '>=2.14.0 <3.0.0'

executables:
  snapshot_analysis: analyse

dependencies:
  args: ^2.0.0
  collection: ^1.15.0
  path: ^1.8.0

# We use 'any' version constraints here as we get our package versions from
# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
# best practice for packages is to specify their compatible version ranges.
# See also https://dart.dev/tools/pub/dependencies.
dev_dependencies:
  lints: any
  test: any

Version history

VersionPublishedArchiveSHA-256
0.1.0On demandDownload3abd7686…2dd265
0.2.0On demandDownload0ff8be89…072fe4
0.3.0On demandDownload17c4cca1…939cc9
0.4.0On demandDownload4d0fe0a2…fe7550
0.5.0On demandDownloadb65ed72a…33e4db
0.5.0+1On demandDownloadade431c1…c75e22
0.5.2On demandDownload5d463e7d…d5c2ef
0.5.3On demandDownloadb27bb3cd…08a49b
0.5.4On demandDownload3ed2a9ce…200fd8
0.5.5On demandDownloadc5c122e3…44709d
0.5.6On demandDownloadd0476763…dd0b53
0.6.0On demandDownloadcdf3d958…ae2977
0.7.0On demandDownloadf9501e87…0ea635
0.7.1On demandDownload7119ddec…2b58b8
0.7.2On demandDownload3bb24a21…cfc4db
0.7.4On demandDownload8f25abd2…923b05
0.7.5On demandDownload003640ed…27157a
0.7.6Cached · Upstream SHA-256 matchedDownload5a79b9fb…1914c8

Dependencies

args "^2.0.0"

collection "^1.15.0"

path "^1.8.0"