// 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 args) async { if (args.length != 2) { print(r""" Usage: run_binary_size_analysis.dart 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 /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.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 addChild( Map node, String kind, String name) { return node['children'].putIfAbsent(name, () { final n = {'n': name, 'k': kind}; if (kind != kindSymbol) { n['children'] = {}; } return n; }); } /// Add the given symbol to the tree. void addSymbol(Map 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(root['maxDepth'], depth); } /// Convert all children entries from maps to lists. Map flatten(Map 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 copyFile(String fromDir, String name, String toDir) async { await new File(p.join(fromDir, name)).copy(p.join(toDir, name)); }