blob: 4a636766b36fc05ce6f6344c28a9cce5341ef138 [file] [log] [blame]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// This tool creates interactive navigatable binary size reports from
// JSON file generated by the AOT compiler's --print-instructions-sizes-to
// flag.
//
// It used the same visualization framework as Chromium's binary_size tool
// located in runtime/third_party/binary_size.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' show max;
import 'package:path/path.dart' as p;
void main(List<String> args) async {
if (args.length != 2) {
print(r"""
Usage: run_binary_size_analysis.dart <symbol-sizes.json> <output-directory>
This tool is used to process snapshot size reports produced by
--print-instructions-sizes-to=symbol-sizes.json.
It will create an interactive web-page in the output-directory which can be
viewed in a browser:
$ google-chrome <output-directory>/index.html
""");
exit(-1);
}
final input = new File(args[0]);
final outputDir = new Directory(args[1]);
// Load symbols data produced by the AOT compiler and convert it to
// a tree.
final symbols = await input
.openRead()
.transform(utf8.decoder)
.transform(json.decoder)
.first;
final root = {'n': '', 'children': {}, 'k': kindPath, 'maxDepth': 0};
for (var symbol in symbols) {
final name = symbol['n'];
final size = symbol['s'];
if (symbol.containsKey('c')) {
final libraryUri = symbol['l'];
final className = symbol['c'];
addSymbol(root, '${libraryUri}/${className}', name, size);
} else {
addSymbol(root, '@stubs', name, size);
}
}
final tree = flatten(root);
// Create output directory and copy all auxiliary files from binary_size tool.
await outputDir.create(recursive: true);
final scriptLocation = p.dirname(Platform.script.toFilePath());
final sdkRoot = p.join(scriptLocation, '..', '..', '..');
final d3SrcDir = p.join(sdkRoot, 'runtime', 'third_party', 'd3', 'src');
final templateDir = p.join(
sdkRoot, 'runtime', 'third_party', 'binary_size', 'src', 'template');
final d3OutDir = p.join(outputDir.path, 'd3');
await new Directory(d3OutDir).create(recursive: true);
for (var file in ['LICENSE', 'd3.js']) {
await copyFile(d3SrcDir, file, d3OutDir);
}
for (var file in ['index.html', 'D3SymbolTreeMap.js']) {
await copyFile(templateDir, file, outputDir.path);
}
// Serialize symbol size tree as JSON.
final dataJsPath = p.join(outputDir.path, 'data.js');
final sink = new File(dataJsPath).openWrite();
sink.write('var tree_data=');
await sink.addStream(new Stream<Object>.fromIterable([tree])
.transform(json.encoder.fuse(utf8.encoder)));
await sink.close();
// Done.
print('Generated ${p.toUri(p.absolute(outputDir.path, 'index.html'))}');
}
const kindSymbol = 's';
const kindPath = 'p';
const kindBucket = 'b';
const symbolTypeGlobalText = 'T';
/// Create a child with the given name within the given node or return
/// an existing child.
Map<String, dynamic> addChild(
Map<String, dynamic> node, String kind, String name) {
return node['children'].putIfAbsent(name, () {
final n = <String, dynamic>{'n': name, 'k': kind};
if (kind != kindSymbol) {
n['children'] = {};
}
return n;
});
}
/// Add the given symbol to the tree.
void addSymbol(Map<String, dynamic> root, String path, String name, int size) {
var node = root;
var depth = 0;
for (var part in path.split('/')) {
node = addChild(node, kindPath, part);
depth++;
}
node['lastPathElement'] = true;
node = addChild(node, kindBucket, symbolTypeGlobalText);
node['t'] = symbolTypeGlobalText;
node = addChild(node, kindSymbol, name);
node['t'] = symbolTypeGlobalText;
node['value'] = size;
depth += 2;
root['maxDepth'] = max<int>(root['maxDepth'], depth);
}
/// Convert all children entries from maps to lists.
Map<String, dynamic> flatten(Map<String, dynamic> node) {
dynamic children = node['children'];
if (children != null) {
children = children.values.map((dynamic v) => flatten(v)).toList();
node['children'] = children;
if (children.length == 1 && children.first['k'] == 'p') {
final singleChild = children.first;
singleChild['n'] = '${node['n']}/${singleChild['n']}';
return singleChild;
}
}
return node;
}
/// Copy file with the given name from [fromDir] to [toDir].
Future<void> copyFile(String fromDir, String name, String toDir) async {
await new File(p.join(fromDir, name)).copy(p.join(toDir, name));
}