ddfde57069
Bug: https://github.com/dart-lang/sdk/issues/36900 Change-Id: Ib2e417f4baa0048e2d4c2156c250d0cf454fdf87 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104523 Commit-Queue: Todd Volkert <tvolkert@google.com> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
148 lines
4.6 KiB
Dart
148 lines
4.6 KiB
Dart
// 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()
|
|
.cast<List<int>>()
|
|
.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));
|
|
}
|