| import 'dart:convert'; |
| import 'dart:io'; |
| import 'usage_exception.dart'; |
| import 'package:args/command_runner.dart'; |
| |
| import 'package:dart2js_info/info.dart'; |
| import 'package:dart2js_info/src/io.dart'; |
| import 'package:vm_snapshot_analysis/treemap.dart'; |
| import 'package:dart2js_info/src/util.dart' show libraryGroupName; |
| import 'package:vm_snapshot_analysis/program_info.dart' as vm; |
| |
| /// Command that converts a `--dump-info` JSON output into a format ingested by Devtools. |
| /// |
| /// Achieves this by converting an [AllInfo] tree into a [vm.ProgramInfo] |
| /// tree, which is then converted into the format ingested by DevTools via a |
| /// [TreeMap] intermediary. Initially built to enable display of code size |
| /// distribution from all info in a dart web app. |
| class DevtoolsFormatCommand extends Command<void> with PrintUsageException { |
| @override |
| final String name = "to_devtools_format"; |
| @override |
| final String description = |
| "Converts dart2js info into a format accepted by Dart Devtools' " |
| "app size analysis panel."; |
| |
| DevtoolsFormatCommand() { |
| argParser.addOption('out', |
| abbr: 'o', help: 'Output treemap.json file (defaults to stdout'); |
| } |
| |
| @override |
| void run() async { |
| final args = argResults!; |
| if (args.rest.isEmpty) { |
| usageException('Missing argument: info.data or info.json'); |
| } |
| final outputPath = args['out']; |
| final AllInfo allInfo = await infoFromFile(args.rest.first); |
| final builder = ProgramInfoBuilder(allInfo); |
| vm.ProgramInfo programInfo = builder.build(allInfo); |
| Map<String, dynamic> treeMap = treemapFromInfo(programInfo); |
| var treeMapJson = jsonEncode(treeMap); |
| if (outputPath == null) { |
| print(treeMapJson); |
| } else { |
| await File(outputPath).writeAsString(treeMapJson); |
| } |
| } |
| } |
| |
| /// Recover [vm.ProgramInfoNode] tree structure from the [AllInfo] profile. |
| /// |
| /// The [vm.ProgramInfoNode] tree has a similar structure to the [AllInfo] tree |
| /// except that the root has packages, libraries, constants, and typedefs as |
| /// immediate children. |
| class ProgramInfoBuilder extends VMProgramInfoVisitor<vm.ProgramInfoNode> { |
| final AllInfo info; |
| |
| final program = vm.ProgramInfo(); |
| |
| final List<vm.ProgramInfoNode> outputInfo = []; |
| |
| /// Mapping between the name of a [Info] object and the corresponding |
| /// [vm.ProgramInfoNode] object. |
| Map<String, vm.ProgramInfoNode> infoNodesByName = {}; |
| |
| /// Mapping between the id (aka coverageId) of an [AllInfo] node and the |
| /// corresponding [vm.ProgramInfoNode] object. |
| final Map<String, vm.ProgramInfoNode> infoNodesById = {}; |
| |
| /// Mapping between package names and the corresponding [vm.ProgramInfoNode] |
| /// objects of [vm.NodeType.packageNode]. |
| final Map<String, vm.ProgramInfoNode> packageInfoNodes = {}; |
| |
| ProgramInfoBuilder(this.info); |
| |
| @override |
| vm.ProgramInfoNode visitAll(AllInfo info) { |
| outputInfo.add(program.root); |
| info.libraries.forEach(makePackage); |
| info.packages.forEach(visitPackage); |
| info.libraries.forEach(makeLibrary); |
| info.libraries.forEach(visitLibrary); |
| info.constants.forEach(makeConstant); |
| info.constants.forEach(visitConstant); |
| return program.root; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitProgram(ProgramInfo info) { |
| throw Exception('Not supported for devtools format.'); |
| } |
| |
| @override |
| vm.ProgramInfoNode visitPackage(PackageInfo info) { |
| info.libraries.forEach(makePackage); |
| info.libraries.forEach(makeLibrary); |
| info.libraries.forEach(visitLibrary); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitLibrary(LibraryInfo info) { |
| info.topLevelFunctions.forEach(makeFunction); |
| info.topLevelFunctions.forEach(visitFunction); |
| info.topLevelVariables.forEach(makeField); |
| info.topLevelVariables.forEach(visitField); |
| info.classes.forEach(makeClass); |
| info.classes.forEach(visitClass); |
| info.classTypes.forEach(makeClassType); |
| info.classTypes.forEach(visitClassType); |
| info.typedefs.forEach(makeTypedef); |
| info.typedefs.forEach(visitTypedef); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitClass(ClassInfo info) { |
| info.functions.forEach(makeFunction); |
| info.functions.forEach(visitFunction); |
| info.fields.forEach(makeField); |
| info.fields.forEach(visitField); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitField(FieldInfo info) { |
| info.closures.forEach(makeClosure); |
| info.closures.forEach(visitClosure); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitFunction(FunctionInfo info) { |
| info.closures.forEach(makeClosure); |
| info.closures.forEach(visitClosure); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitClassType(ClassTypeInfo info) { |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitClosure(ClosureInfo info) { |
| makeFunction(info.function); |
| visitFunction(info.function); |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitConstant(ConstantInfo info) { |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitTypedef(TypedefInfo info) { |
| return infoNodesByName[info.name]!; |
| } |
| |
| @override |
| vm.ProgramInfoNode visitOutput(OutputUnitInfo info) { |
| throw Exception("For deferred loading."); |
| } |
| |
| vm.ProgramInfo build(AllInfo info) { |
| visitAll(info); |
| return program; |
| } |
| |
| /// Collect libraries into packages and aggregate their sizes. |
| void makePackage(LibraryInfo libraryInfo) { |
| String packageName = libraryGroupName(libraryInfo) ?? libraryInfo.name; |
| vm.ProgramInfoNode? packageInfoNode = packageInfoNodes[packageName]; |
| if (packageInfoNode == null) { |
| vm.ProgramInfoNode newPackage = program.makeNode( |
| name: packageName, |
| parent: program.root, |
| type: vm.NodeType.packageNode); |
| newPackage.size = libraryInfo.size; |
| packageInfoNodes[packageName] = newPackage; |
| program.root.children[packageName] = newPackage; |
| outputInfo.add(newPackage); |
| infoNodesByName[newPackage.name] = newPackage; |
| } else { |
| packageInfoNode.size = (packageInfoNode.size ?? 0) + libraryInfo.size; |
| } |
| } |
| |
| void makeLibrary(LibraryInfo libraryInfo) { |
| String packageName = libraryGroupName(libraryInfo) ?? libraryInfo.name; |
| vm.ProgramInfoNode? parentNode = infoNodesByName[packageName]; |
| vm.ProgramInfoNode newLibrary = program.makeNode( |
| name: libraryInfo.name, |
| parent: parentNode!, |
| type: vm.NodeType.libraryNode); |
| newLibrary.size = libraryInfo.size; |
| parentNode.children[newLibrary.name] = newLibrary; |
| infoNodesByName[newLibrary.name] = newLibrary; |
| outputInfo.add(newLibrary); |
| } |
| |
| void makeFunction(FunctionInfo functionInfo) { |
| Info? parent = functionInfo.parent; |
| if (parent != null) { |
| vm.ProgramInfoNode? parentNode = infoNodesByName[parent.name]; |
| vm.ProgramInfoNode newFunction = program.makeNode( |
| name: functionInfo.name, |
| parent: parentNode!, |
| type: vm.NodeType.functionNode); |
| newFunction.size = functionInfo.size; |
| parentNode.children[newFunction.name] = newFunction; |
| infoNodesByName[newFunction.name] = newFunction; |
| outputInfo.add(newFunction); |
| } |
| } |
| |
| void makeClass(ClassInfo classInfo) { |
| Info? parent = classInfo.parent; |
| if (parent != null) { |
| vm.ProgramInfoNode? parentNode = infoNodesByName[parent.name]; |
| vm.ProgramInfoNode newClass = program.makeNode( |
| name: classInfo.name, |
| parent: parentNode!, |
| type: vm.NodeType.classNode); |
| newClass.size = classInfo.size; |
| parentNode.children[newClass.name] = newClass; |
| infoNodesByName[newClass.name] = newClass; |
| outputInfo.add(newClass); |
| } |
| } |
| |
| /// Fields are currently assigned [vm.NodeType.other]. |
| /// |
| /// Note: we might want to create a separate [vm.NodeType.fieldNode] to |
| /// differentiate fields from other miscellaneous nodes for constructing |
| /// the call graph. |
| void makeField(FieldInfo fieldInfo) { |
| Info? parent = fieldInfo.parent; |
| if (parent != null) { |
| vm.ProgramInfoNode? parentNode = infoNodesByName[parent.name]; |
| vm.ProgramInfoNode newField = program.makeNode( |
| name: fieldInfo.name, parent: parentNode!, type: vm.NodeType.other); |
| newField.size = fieldInfo.size; |
| parentNode.children[newField.name] = newField; |
| infoNodesByName[newField.name] = newField; |
| outputInfo.add(newField); |
| } |
| } |
| |
| void makeConstant(ConstantInfo constantInfo) { |
| vm.ProgramInfoNode newConstant = program.makeNode( |
| name: constantInfo.name, parent: program.root, type: vm.NodeType.other); |
| newConstant.size = constantInfo.size; |
| program.root.children[newConstant.name] = newConstant; |
| infoNodesByName[newConstant.name] = newConstant; |
| outputInfo.add(newConstant); |
| } |
| |
| void makeTypedef(TypedefInfo typedefInfo) { |
| vm.ProgramInfoNode newTypedef = program.makeNode( |
| name: typedefInfo.name, parent: program.root, type: vm.NodeType.other); |
| newTypedef.size = typedefInfo.size; |
| infoNodesByName[newTypedef.name] = newTypedef; |
| outputInfo.add(newTypedef); |
| } |
| |
| void makeClassType(ClassTypeInfo classTypeInfo) { |
| Info? parent = classTypeInfo.parent; |
| if (parent != null) { |
| vm.ProgramInfoNode? parentNode = infoNodesByName[parent.name]; |
| vm.ProgramInfoNode newClassType = program.makeNode( |
| name: classTypeInfo.name, |
| parent: parentNode!, |
| type: vm.NodeType.other); |
| newClassType.size = classTypeInfo.size; |
| infoNodesByName[newClassType.name] = newClassType; |
| outputInfo.add(newClassType); |
| } |
| } |
| |
| void makeClosure(ClosureInfo closureInfo) { |
| Info? parent = closureInfo.parent; |
| if (parent != null) { |
| vm.ProgramInfoNode? parentNode = infoNodesByName[parent.name]; |
| vm.ProgramInfoNode newClosure = program.makeNode( |
| name: closureInfo.name, |
| parent: parentNode!, |
| // ProgramInfo trees consider closures and functions to both be of the functionNode type. |
| type: vm.NodeType.functionNode); |
| newClosure.size = closureInfo.size; |
| parentNode.children[newClosure.name] = newClosure; |
| infoNodesByName[newClosure.name] = newClosure; |
| outputInfo.add(newClosure); |
| } |
| } |
| } |