| [![pub package](https://img.shields.io/pub/v/vm_snapshot_analysis.svg)](https://pub.dev/packages/vm_snapshot_analysis) |
| [![package publisher](https://img.shields.io/pub/publisher/vm_snapshot_analysis.svg)](https://pub.dev/packages/vm_snapshot_analysis/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: |
| |
| ```console |
| $ 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: |
| |
| ```console |
| $ 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 |
| |
| ```console |
| $ pub global activate vm_snapshot_analysis |
| ``` |
| |
| `snapshot_analysis` supports the following subcommands: |
| |
| ### `summary` |
| |
| ```console |
| $ 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: |
| |
| ```console |
| $ 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=`). |
| |
| ```console |
| $ 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: |
| |
| ```console |
| $ 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` |
| |
| ```console |
| $ 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. |
| |
| ```console |
| $ 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` |
| |
| ```console |
| $ 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` |
| |
| ```console |
| $ 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. |
| |
| ```console |
| 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: |
| |
| ```json |
| { |
| "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][tracker]. |
| |
| [tracker]: https://github.com/dart-lang/sdk/issues |