| // 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. |
| library vm_snapshot_analysis.commands.treemap; |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:isolate'; |
| |
| import 'package:path/path.dart' as p; |
| import 'package:args/command_runner.dart'; |
| |
| import 'package:vm_snapshot_analysis/treemap.dart'; |
| import 'package:vm_snapshot_analysis/utils.dart'; |
| |
| class TreemapCommand extends Command<void> { |
| @override |
| final name = 'treemap'; |
| |
| @override |
| final description = ''' |
| Create interactive treemap from snapshot profiling information. |
| |
| This tool is used to process snapshot size reports produced by |
| --print-instructions-sizes-to and --write-v8-snapshot-profile-to flags. |
| |
| It will create an interactive web-page in the output-directory which can be |
| viewed in a browser: |
| |
| \$ google-chrome <output-directory>/index.html'''; |
| |
| @override |
| String get invocation => super |
| .invocation |
| .replaceAll('[arguments]', '<symbol-sizes.json> <output-directory>'); |
| |
| TreemapCommand() { |
| // [argParser] is automatically created by the parent class. |
| argParser |
| ..addFlag('force', |
| abbr: 'f', |
| help: 'Force overwrite output directory even if it already exists') |
| ..addOption('format', |
| help: 'Specifies granularity of the treemap output', |
| allowed: _formatFromString.keys, |
| defaultsTo: _formatFromString.keys.first); |
| } |
| |
| @override |
| Future<void> run() async { |
| if (argResults.rest.length != 2) { |
| usageException('Need to specify input JSON and output directory.'); |
| } |
| |
| final input = File(argResults.rest[0]); |
| final outputDir = Directory(argResults.rest[1]); |
| |
| if (!input.existsSync()) { |
| usageException('Input file ${input.path} does not exist!'); |
| } |
| |
| if (outputDir.existsSync() && !argResults['force']) { |
| usageException( |
| 'Output directory ${outputDir.path} already exists, specify --force to ignore.'); |
| } |
| |
| await generateTreeMap(input, outputDir, |
| format: _formatFromString[argResults['format']]); |
| } |
| |
| // Note: the first key in this map is the default format. |
| static const _formatFromString = { |
| 'object-type': TreemapFormat.objectType, |
| 'data-and-code': TreemapFormat.dataAndCode, |
| 'collapsed': TreemapFormat.collapsed, |
| 'simplified': TreemapFormat.simplified, |
| }; |
| } |
| |
| Future<void> generateTreeMap(File input, Directory outputDir, |
| {TreemapFormat format = TreemapFormat.objectType}) async { |
| // Load symbols data produced by the AOT compiler and convert it to |
| // a tree. |
| final inputJson = await loadJson(input); |
| final tree = treemapFromJson(inputJson, format: format); |
| |
| // Create output directory and copy all auxiliary files from binary_size tool. |
| await outputDir.create(recursive: true); |
| |
| final assetsUri = await Isolate.resolvePackageUri( |
| Uri.parse('package:vm_snapshot_analysis/src/assets')); |
| final assetsDir = assetsUri.toFilePath(); |
| final d3SrcDir = p.join(assetsDir, 'd3', 'src'); |
| |
| final d3OutDir = p.join(outputDir.path, 'd3'); |
| await 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(assetsDir, file, outputDir.path); |
| } |
| |
| // Serialize symbol size tree as JSON. |
| final dataJsPath = p.join(outputDir.path, 'data.js'); |
| final sink = File(dataJsPath).openWrite(); |
| sink.write('var tree_data='); |
| await sink.addStream(Stream<Object>.fromIterable([tree]) |
| .transform(json.encoder.fuse(utf8.encoder))); |
| await sink.close(); |
| |
| // Done. |
| print('Generated ${p.toUri(p.absolute(outputDir.path, 'index.html'))}'); |
| } |
| |
| /// Copy file with the given name from [fromDir] to [toDir]. |
| Future<void> copyFile(String fromDir, String name, String toDir) async { |
| await File(p.join(fromDir, name)).copy(p.join(toDir, name)); |
| } |