blob: 7d668a9809d75528f91846533db4635eeaa56267 [file] [view]
# Tools for Analyzing Chrome's Binary Size
These tools currently focus on supporting Android. They somewhat work with
Linux builds. As for Windows, some great tools already exist and are documented
here:
* https://www.chromium.org/developers/windows-binary-sizes
There is also a dedicated mailing-list for binary size discussions:
* https://groups.google.com/a/chromium.org/forum/#!forum/binary-size
Bugs and feature requests are tracked in crbug under:
* [Tools > BinarySize](https://bugs.chromium.org/p/chromium/issues/list?q=component%3ATools>BinarySize)
Per-Milestone Binary Size Breakdowns:
* https://chrome-supersize.firebaseapp.com/chrome-supersize/index.html
[TOC]
## Binary Size Trybot (android-binary-size)
* Introduced October 2018 as a mandatory CQ bot.
* Documented [here](/docs/speed/binary_size/android_binary_size_trybot.md).
## Binary Size Gerrit Plugin
* Currently in development, should hopefully be launched soon.
* This bot surfaces the info from the binary size trybot on the cl view page
itself.
* Surfacing binary size impact for a change allows authors and reviewers
to easily assess whether or not it makes sense for the change.
* Shows you only the binary size metrics your patchset affects.
* Links to SuperSize html and text output for more details on which symbols
were changed.
* For tips on reducing binary size, see [Optimization Advice][optimization_advice].
[optimization_advice]: //docs/speed/binary_size/optimization_advice.md
## resource_sizes.py
* [//build/android/resource_sizes.py](https://cs.chromium.org/chromium/src/build/android/resource_sizes.py)
* Able to run on an `.apk` without having the build directory available.
* Reports the size metrics captured by our perf builders. Viewable at
[chromeperf](https://chromeperf.appspot.com/report) under
`Test suite="resource_sizes ($APK)"`.
* Metrics reported by this tool are described in
[//docs/speed/binary_size/metrics.md](//docs/speed/binary_size/metrics.md).
## SuperSize
Collects, archives, and analyzes Chrome's binary size.
Supports Android and Linux (although Linux
[has issues](https://bugs.chromium.org/p/chromium/issues/detail?id=717550)).
### Technical Details
#### What's in a .size File?
`.size` files are gzipped plain text files that contain:
1. A list of section sizes, including:
* .so sections as reported by `readelf -S`
* .pak and .dex sections for apk files
1. Metadata (apk size, GN args, filenames, timestamps, git revision, build id),
1. A list of symbols, including name, address, size,
padding (caused by alignment), and associated source/object files.
#### How are Symbols Collected?
##### Native Symbols (.text, .rodata, .data, .data.rel.ro, .bss)
1. Symbol list is extracted from linker `.map` file.
* Map files contain some unique pieces of information compared to `nm`
output, such as `** merge strings` entries, and some unnamed symbols
(which although unnamed, contain the `.o` path).
* Generated in `is_official_build=true` builds if `generate_linker_map` is
true. In official builds on Android generate_linker_map is true by default.
1. `.o` files are mapped to `.cc` files by parsing `.ninja` files.
* This means that `.h` files are never listed as sources. No information
about inlined symbols is gathered.
1. `** merge strings` symbols are further broken down into individual string
literal symbols. This is done by reading string literals from `.o` files, and
then searching for them within the `** merge strings` sections.
* For LLD with [ThinLTO](https://clang.llvm.org/docs/ThinLTO.html),
`llvm-bcanalyzer` is used to extract string literals.
1. Symbol aliases:
* Aliases have the same address and size, but report their `.pss` as
`.size / .num_aliases`.
* Type 1: Different names. Caused by identical code folding.
* These are collected from debug information via `nm elf-file`.
* Type 2: Same names, different paths. Caused by inline functions defined in
`.h` files.
* These are collected by running `nm` on each `.o` file.
* For LLD with ThinLTO, `llvm-bcanalyzer` is used to process `.o` files,
which are actually LLVM Bitcode files.
* Normally represented using one alias per path, but are sometimes
collapsed into a single symbol with a path of `{shared}/$SYMBOL_COUNT`.
This collapsing is done only for symbols owned by a large number of
paths.
* Type 3: String literals that are de-duped at link-time.
* These are found as part of the string literal extraction process.
##### Pak Symbols (.pak.nontranslated and .pak.translations)
1. Grit creates a mapping between numeric id and textual id for grd files.
* A side effect of pak whitelist generation is a mapping of `.cc` to numeric
id.
* A complete per-apk mapping of numeric id to textual id is stored in the
`output_dir/size-info` dir.
1. `supersize` uses these two mappings to find associated source files for the
pak entries found in all of the apk's `.pak` files.
* Pak entries with the same name are merged into a single symbol.
* This is the case of pak files for translations.
* The original grd file paths are stored in the full name of each symbol.
##### Dex Symbols (.dex and .dex.method)
1. Java compile targets create a mapping between java fully qualified names
(FQN) and source files.
* For `.java` files the FQN of the public class is mapped to the file.
* For `.srcjar` files the FQN of the public class is mapped to the `.srcjar`
file path.
* A complete per-apk class FQN to source mapping is stored in the
`output_dir/size-info` dir.
1. The `apkanalyzer` sdk tool is used to find the size and FQN of entries in
the dex file.
* If a proguard `.mapping` file is available, that is used to get back the
original FQN.
1. The output from `apkanalyzer` is used by `supersize` along with the mapping
file to find associated source files for the dex entries found in all of the
apk's `.dex` files.
##### Other Symbols (.other)
All files in an apk that are not broken down into sub-entries are tracked by a
symbol within the `.other` section.
##### Overhead and Star Symbols
Overhead symbols track bytes that are generally unactionable. They are recorded
as `size=0, padding=$size` (padding-only symbols) to de-emphasize them in diffs.
Star symbols are those that track sections of the binary that are not padding,
but which the tool is not able to break down further
(e.g. "\*\* Merge Globals")
* **\*\* symbol gap**: A gap between symbols that is larger than what could be
due to alignment.
* **Overhead: ELF file**: `elf_file_size - sum(elf_sections)`.
* Captures bytes taken up by ELF headers and section alignment.
* **Overhead: APK file**: `apk_file_size - sum(compressed_file_sizes)`
* Captures bytes taken up by `.zip` metadata and zipalign padding.
* **Overhead: ${NAME}.pak**: `pak_file_size - sum(pak_entries)`
* **Overhead: Pak compression artifacts**:
`compressed_size_of_paks - sum(pak_entries)`
* It would be possible to correctly attribute compressed size to pak symbols,
but doing so makes diffs very noisy (any change in compression ratio causes
every symbol to change by a small amount). Instead, SuperSize uses a
hard-coded compression ratio for compressed .pak symbols, and captures any
remainder in this overhead symbol.
* TODO([crbug/894320](https://crbug.com/894320)): Improve how compression is
tracked.
#### What Other Processing Happens?
1. Path normalization:
* Prefixes are removed: `out/Release/`, `gen/`, `obj/`
* Archive names made more pathy: `foo/bar.a(baz.o)` -> `foo/bar.a/baz.o`
* Shared symbols do not store the complete source paths. Instead, the
common ancestor is computed and stored as the path.
* Example: `base/{shared}/3` (the "3" means three different files contain
the symbol)
1. Name normalization:
* `(anonymous::)` is removed from names (and stored as a symbol flag).
* `[clone]` suffix removed (and stored as a symbol flag).
* `vtable for FOO` -> `Foo [vtable]`
* Mangling done by linkers is undone (e.g. prefixing with "unlikely.")
* Names are processed into:
* `name`: Name without template and argument parameters
* `template_name`: Name without argument parameters.
* `full_name`: Name with all parameters.
1. Special cases:
* LLVM function outlining creates many OUTLINED_FUNCTION_* symbols. These
renamed to '** outlined functions' or '** outlined functions * (count)',
and are deduped so an address can have at most one such symbol.
1. Clustering:
* Compiler & linker optimizations can cause symbols to be broken into
multiple parts to become candidates for inlining ("partial inlining").
* These symbols are sometimes suffixed with "`[clone]`" (removed by
normalization).
* Clustering creates groups containing all pieces of a symbol (in the case
where multiple pieces remain after inlining).
* Clustering is done by default on `SizeInfo.symbols`. To view unclustered
symbols, use `SizeInfo.raw_symbols`.
1. Diffing:
* Some heuristics for matching up before/after symbols.
1. Simulated compression:
* Only some `.pak` files are compressed and others are kept uncompressed.
* To get a reasonable idea of actual impact to final apk size, we use a
constant compression factor for all the compressed `.pak` files.
* This prevents swings in compressed sizes for all symbols when new
entries are added or old entries are removed.
* The constant is chosen so that it minimizes overall discrepancy with
actual total compressed sizes.
#### Is SuperSize a Generic Tool?
No. Some examples of why it's Chrome-specific:
* Assumes `.ninja` build rules are available.
* Heuristic for locating `.so` given `.apk`.
* Requires `size-info` dir in output directory to analyze `.pak` and `.dex`
files.
### Usage: archive
Collect size information and dump it into a `.size` file.
*** note
**Note:** Refer to
[diagnose_bloat.py](https://cs.chromium.org/search/?q=file:diagnose_bloat.py+gn_args)
for list of GN args to build a Release binary (or just use the tool with --single).
***
Example Usage:
``` bash
# Android:
ninja -C out/Release -j 1000 apks/ChromePublic.apk
tools/binary_size/supersize archive chrome.size --apk-file out/Release/apks/ChromePublic.apk -v
# Linux:
ninja -C out/Release -j 1000 chrome
tools/binary_size/supersize archive chrome.size --elf-file out/Release/chrome -v
```
### Usage: html_report
Creates an `.ndjson` (newline-delimited JSON) file that the
[SuperSize viewer](https://chrome-supersize.firebaseapp.com/viewer.html)
is able to load.
Example Usage:
``` bash
# Creates the data file ./report.ndjson, generated based on ./chrome.size
tools/binary_size/supersize html_report chrome.size report.ndjson -v
# Includes every symbol in the data file, although it will take longer to load.
tools/binary_size/supersize html_report chrome.size report.ndjson --all-symbols
# Create a data file showing a diff between two .size files.
tools/binary_size/supersize html_report after.size --diff-with before.size report.ndjson
```
### Usage: start_server
Locally view the `.ndjson` file generated by `html_report`, by starting a web
server that links to the file.
Example Usage:
``` bash
# Starts a local server to view the data in ./report.ndjson
tools/binary_size/supersize start_server report.ndjson
# Set a custom address and port.
tools/binary_size/supersize start_server report.ndjson -a localhost -p 8080
```
### Usage: diff
A convenience command equivalent to:
`console before.size after.size --query='Print(Diff(size_info1, size_info2))'`
Example Usage:
``` bash
tools/binary_size/supersize diff before.size after.size --all
```
### Usage: console
Starts a Python interpreter where you can run custom queries, or run pre-made
queries from `canned_queries.py`.
Example Usage:
```bash
# Prints size infomation and exits (does not enter interactive mode).
tools/binary_size/supersize console chrome.size --query='Print(size_info)'
# Enters a Python REPL (it will print more guidance).
tools/binary_size/supersize console chrome.size
```
Example session:
``` python
>>> ShowExamples() # Get some inspiration.
...
>>> sorted = size_info.symbols.WhereInSection('t').Sorted()
>>> Print(sorted) # Have a look at the largest symbols.
...
>>> sym = sorted.WhereNameMatches('TrellisQuantizeBlock')[0]
>>> Disassemble(sym) # Time to learn assembly.
...
>>> help(canned_queries)
...
>>> Print(canned_queries.TemplatesByName(depth=-1))
...
>>> syms = size_info.symbols.WherePathMatches(r'skia').Sorted()
>>> Print(syms, verbose=True) # Show full symbol names with parameter types.
...
>>> # Dump all string literals from skia files to "strings.txt".
>>> Print((t[1] for t in ReadStringLiterals(syms)), to_file='strings.txt')
```
## diagnose_bloat.py
Determines the cause of binary size bloat between two commits. Works for Android
and Linux (although Linux symbol diffs have issues, as noted below).
### How it Works
1. Builds multiple revisions using release GN args.
* Default is to build just two revisions (before & after commit)
1. Measures all outputs using `resource_size.py` and `supersize`.
1. Saves & displays a breakdown of the difference in binary sizes.
### Example Usage
``` bash
# Build and diff monochrome_public_apk HEAD^ and HEAD.
tools/binary_size/diagnose_bloat.py HEAD -v
# Build and diff monochrome_apk HEAD^ and HEAD.
tools/binary_size/diagnose_bloat.py HEAD --enable-chrome-android-internal -v
# Build and diff monochrome_public_apk HEAD^ and HEAD without is_official_build.
tools/binary_size/diagnose_bloat.py HEAD --gn-args="is_official_build=false" -v
# Build and diff all contiguous revs in range BEFORE_REV..AFTER_REV for src/v8.
tools/binary_size/diagnose_bloat.py AFTER_REV --reference-rev BEFORE_REV --subrepo v8 --all -v
# Display detailed usage info (there are many options).
tools/binary_size/diagnose_bloat.py -h
```
## Other Size Tools
### Bloaty McBloatface
* https://github.com/google/bloaty
* Our usage tracked in [crbug/698733](https://crbug.com/698733)