blob: 059d3532e470e6ede574903b6172aa4119b74d9b [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.
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 '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 loadJsonFromFile(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));
}