From dfcbd5987a34eca255ac66c2af762150f562db06 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 15 Jun 2017 14:58:38 -0700 Subject: [PATCH] add diff tool (#19) * add diff tool --- CHANGELOG.md | 4 ++ README.md | 89 ++++++++++++++++++------- bin/diff.dart | 92 ++++++++++++++++++++++++++ lib/info.dart | 22 +++---- lib/src/diff.dart | 165 ++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 3 +- 6 files changed, 339 insertions(+), 36 deletions(-) create mode 100644 bin/diff.dart create mode 100644 lib/src/diff.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3428baa39b4..961ef9e4f604d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.5 + +- Added `diff` tool. + ## 0.5.4+2 - Updated minimum SDK dependency to align with package dependencies. diff --git a/README.md b/README.md index 0d4dd422f7d71..b4c6e54462c9a 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ The following tools are a available today: dependency between functions and fields in your program. Currently it only supports the `some_path` query, which shows a dependency path from one function to another. + + * [`diff`][diff]: a tool that diffs two info files and reports which + program elements have been added, removed, or changed size. This also + tells which elements are no longer deferred or have become deferred. * [`library_size_split`][lib_split]: a tool that shows how much code was attributed to each library. This tool is configurable so it can group data @@ -102,10 +106,10 @@ this tool only supports the `some_path` query, which gives you the shortest path for how one function depends on another. Run this tool as follows: -```bash +```console # activate is only needed once to install the dart2js_info* executables -pub global activate dart2js_info -dart2js_info_code_deps out.js.info.json some_path main foo +$ pub global activate dart2js_info +$ dart2js_info_code_deps out.js.info.json some_path main foo ``` The arguments to the query are regular expressions that can be used to @@ -120,22 +124,58 @@ a fully qualified element name, which includes the library and class name If the name of a function your are looking for is unique enough, it might be sufficient to just write that name as your regular expression. +### Diff tool + +This command-line tool shows a diff between two info files. It can be run +as follows: + +```console +$ pub global activate dart2js_info # only needed once +$ dart2js_info_diff old.js.info.json new.js.info.json +``` + +The tool gives a breakdown of the difference between the two info files. +Here's an example output: + +``` +OVERALL SIZE DIFFERENCE +======================================================================== +3 bytes + +ADDED +======================================================================== + +REMOVED +======================================================================== +file:///home/het/Code/foo/foo.dart::A.y: 0 bytes + +CHANGED SIZE +======================================================================== + +BECAME DEFERRED +======================================================================== + +NO LONGER DEFERRED +======================================================================== + +``` + ### Library size split tool This command-line tool shows the size distribution of generated code among libraries. It can be run as follows: -```bash -pub global activate dart2js_info # only needed once -dart2js_info_library_size_split out.js.info.json +```console +$ pub global activate dart2js_info # only needed once +$ dart2js_info_library_size_split out.js.info.json ``` Libraries can be grouped using regular expressions. You can specify what regular expressions to use by providing a `grouping.yaml` file: -```bash -dart2js_info_library_size_split out.js.info.json grouping.yaml +```console +$ dart2js_info_library_size_split out.js.info.json grouping.yaml ``` The format of the `grouping.yaml` file is as follows: @@ -205,9 +245,9 @@ bootstrapping code and lazy static initializers are missing. This tool checks that the output from dart2js meets a given specification, given in a YAML file. It can be run as follows: -```bash -pub global activate dart2js_info # only needed once -dart2js_info_deferred_library_check out.js.info.json manifest.yaml +```console +$ pub global activate dart2js_info # only needed once +$ dart2js_info_deferred_library_check out.js.info.json manifest.yaml ``` The format of the YAML file is: @@ -253,7 +293,7 @@ This tool gives a breakdown of all of the deferred code in the program by size. It can show how much of the total code size is deferred. It can be run as follows: -```bash +```console pub global activate dart2js_info # only needed once dart2js_info_deferred_library_size out.js.info.json ``` @@ -278,9 +318,9 @@ Percent of code deferred 41.86% This tool reports which code is included in each output unit. It can be run as follows: -```bash -pub global activate dart2js_info # only needed once -dart2js_info_deferred_library_layout out.js.info.json +```console +$ pub global activate dart2js_info # only needed once +$ dart2js_info_deferred_library_layout out.js.info.json ``` The tool will output a table listing all of the deferred output units or chunks, @@ -322,9 +362,9 @@ code of your application. We use dependency information to compute dominance and reachability data as well. When you run: -```bash -pub global activate dart2js_info # only needed once -dart2js_info_function_size_analysis out.js.info.json +```console +$ pub global activate dart2js_info # only needed once +$ dart2js_info_function_size_analysis out.js.info.json ``` the tool produces a table output with lots of entries. Here is an example entry @@ -355,8 +395,8 @@ steps are as follows: * Compile an app with dart2js using `--dump-info` and defining the Dart environment `traceCalls=post`: -``` -DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart +```console +$ DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart ``` Because coverage/tracing data is currently experimental, the feature is @@ -365,8 +405,8 @@ DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart * Launch the coverage server tool to serve up the JS code of your app: -```bash -dart2js_info_coverage_log_server main.dart.js +```console +$ dart2js_info_coverage_log_server main.dart.js ``` * (optional) If you have a complex application setup, you may need to serve an @@ -382,8 +422,8 @@ dart2js_info_coverage_log_server main.dart.js * Finally, run the live code analysis tool given it both the info and coverage json files: -```bash -dart2js_info_live_code_size_analysis main.dart.info.json main.dart.coverage.json +```console +$ dart2js_info_live_code_size_analysis main.dart.info.json main.dart.coverage.json ``` ## Code location, features and bugs @@ -394,6 +434,7 @@ bugs at the [issue tracker][tracker]. [repo]: https://github.com/dart-lang/dart2js_info/ [tracker]: https://github.com/dart-lang/dart2js_info/issues [code_deps]: https://github.com/dart-lang/dart2js_info/blob/master/bin/code_deps.dart +[diff]: https://github.com/dart-lang/dart2js_info/blob/master/bin/diff.dart [lib_split]: https://github.com/dart-lang/dart2js_info/blob/master/bin/library_size_split.dart [deferred_lib]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_check.dart [deferred_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_size.dart diff --git a/bin/diff.dart b/bin/diff.dart new file mode 100644 index 0000000000000..eb73e4afd03a6 --- /dev/null +++ b/bin/diff.dart @@ -0,0 +1,92 @@ +// Copyright (c) 2017, 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. + +import 'package:dart2js_info/src/diff.dart'; +import 'package:dart2js_info/src/util.dart'; + +/// A command-line tool that computes the diff between two info files. +main(List args) async { + if (args.length != 2) { + print('usage: dart2js_info_diff old.info.json new.info.json'); + return; + } + var oldInfo = await infoFromFile(args[0]); + var newInfo = await infoFromFile(args[1]); + + var diffs = diff(oldInfo, newInfo); + + // Categorize the diffs + var adds = []; + var removals = []; + var sizeChanges = []; + var becameDeferred = []; + var becameUndeferred = []; + + for (var diff in diffs) { + switch (diff.kind) { + case DiffKind.add: + adds.add(diff as AddDiff); + break; + case DiffKind.remove: + removals.add(diff as RemoveDiff); + break; + case DiffKind.size: + sizeChanges.add(diff as SizeDiff); + break; + case DiffKind.deferred: + var deferredDiff = diff as DeferredStatusDiff; + if (deferredDiff.wasDeferredBefore) { + becameUndeferred.add(deferredDiff); + } else { + becameDeferred.add(deferredDiff); + } + break; + } + } + + // TODO(het): Improve this output. Siggi has good suggestions in + // https://github.com/dart-lang/dart2js_info/pull/19 + var overallSizeDiff = newInfo.program.size - oldInfo.program.size; + _section('OVERALL SIZE DIFFERENCE'); + print('$overallSizeDiff bytes'); + print(''); + + _section('ADDED'); + for (var add in adds) { + print('${longName(add.info, useLibraryUri: true)}: ${add.info.size} bytes'); + } + print(''); + + _section('REMOVED'); + for (var removal in removals) { + print('${longName(removal.info, useLibraryUri: true)}: ' + '${removal.info.size} bytes'); + } + print(''); + + _section('CHANGED SIZE'); + for (var sizeChange in sizeChanges) { + print('${longName(sizeChange.info, useLibraryUri: true)}: ' + '${sizeChange.sizeDifference} bytes'); + } + print(''); + + _section('BECAME DEFERRED'); + for (var diff in becameDeferred) { + print('${longName(diff.info, useLibraryUri: true)}: ' + '${diff.info.size} bytes'); + } + print(''); + + _section('NO LONGER DEFERRED'); + for (var diff in becameUndeferred) { + print('${longName(diff.info, useLibraryUri: true)}: ' + '${diff.info.size} bytes'); + } +} + +void _section(String title) { + print(title); + print('=' * 72); +} diff --git a/lib/info.dart b/lib/info.dart index 686c4b10cb687..f3baa40587735 100644 --- a/lib/info.dart +++ b/lib/info.dart @@ -46,7 +46,7 @@ abstract class Info { /// Info of the enclosing element. Info parent; - dynamic accept(InfoVisitor visitor); + T accept(InfoVisitor visitor); } /// Common information used for most kind of elements. @@ -171,7 +171,7 @@ class AllInfo { AllInfo(); - dynamic accept(InfoVisitor visitor) => visitor.visitAll(this); + T accept(InfoVisitor visitor) => visitor.visitAll(this); } class ProgramInfo { @@ -196,7 +196,7 @@ class ProgramInfo { this.noSuchMethodEnabled, this.minified}); - dynamic accept(InfoVisitor visitor) => visitor.visitProgram(this); + T accept(InfoVisitor visitor) => visitor.visitProgram(this); } /// Info associated with a library element. @@ -229,7 +229,7 @@ class LibraryInfo extends BasicInfo { LibraryInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitLibrary(this); + T accept(InfoVisitor visitor) => visitor.visitLibrary(this); } /// Information about an output unit. Normally there is just one for the entire @@ -244,7 +244,7 @@ class OutputUnitInfo extends BasicInfo { OutputUnitInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitOutput(this); + T accept(InfoVisitor visitor) => visitor.visitOutput(this); } /// Information about a class element. @@ -267,7 +267,7 @@ class ClassInfo extends BasicInfo { ClassInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitClass(this); + T accept(InfoVisitor visitor) => visitor.visitClass(this); } /// Information about a constant value. @@ -282,7 +282,7 @@ class ConstantInfo extends BasicInfo { ConstantInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitConstant(this); + T accept(InfoVisitor visitor) => visitor.visitConstant(this); } /// Information about a field element. @@ -319,7 +319,7 @@ class FieldInfo extends BasicInfo with CodeInfo { FieldInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitField(this); + T accept(InfoVisitor visitor) => visitor.visitField(this); } /// Information about a typedef declaration. @@ -332,7 +332,7 @@ class TypedefInfo extends BasicInfo { TypedefInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitTypedef(this); + T accept(InfoVisitor visitor) => visitor.visitTypedef(this); } /// Information about a function or method. @@ -396,7 +396,7 @@ class FunctionInfo extends BasicInfo with CodeInfo { FunctionInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitFunction(this); + T accept(InfoVisitor visitor) => visitor.visitFunction(this); } /// Information about a closure, also known as a local function. @@ -410,7 +410,7 @@ class ClosureInfo extends BasicInfo { ClosureInfo._(String serializedId) : super._fromId(serializedId); - dynamic accept(InfoVisitor visitor) => visitor.visitClosure(this); + T accept(InfoVisitor visitor) => visitor.visitClosure(this); } /// Information about how a dependency is used. diff --git a/lib/src/diff.dart b/lib/src/diff.dart new file mode 100644 index 0000000000000..2d43f28d1567e --- /dev/null +++ b/lib/src/diff.dart @@ -0,0 +1,165 @@ +import 'package:dart2js_info/info.dart'; +import 'package:dart2js_info/src/util.dart'; + +class Diff { + final BasicInfo info; + final DiffKind kind; + Diff(this.info, this.kind); +} + +enum DiffKind { add, remove, size, deferred } + +class RemoveDiff extends Diff { + RemoveDiff(BasicInfo info) : super(info, DiffKind.remove); +} + +class AddDiff extends Diff { + AddDiff(BasicInfo info) : super(info, DiffKind.add); +} + +class SizeDiff extends Diff { + final int sizeDifference; + SizeDiff(BasicInfo info, this.sizeDifference) : super(info, DiffKind.size); +} + +class DeferredStatusDiff extends Diff { + final bool wasDeferredBefore; + DeferredStatusDiff(BasicInfo info, this.wasDeferredBefore) + : super(info, DiffKind.deferred); +} + +List diff(AllInfo oldInfo, AllInfo newInfo) { + var differ = new _InfoDiffer(oldInfo, newInfo); + differ.diff(); + return differ.diffs; +} + +class _InfoDiffer extends InfoVisitor { + final AllInfo _old; + final AllInfo _new; + + BasicInfo _other; + + List diffs = []; + + _InfoDiffer(this._old, this._new); + + void diff() { + _diffList(_old.libraries, _new.libraries); + } + + @override + visitAll(AllInfo info) { + throw new StateError('should not diff AllInfo'); + } + + @override + visitProgram(ProgramInfo info) { + throw new StateError('should not diff ProgramInfo'); + } + + @override + visitOutput(OutputUnitInfo info) { + throw new StateError('should not diff OutputUnitInfo'); + } + + // TODO(het): diff constants + @override + visitConstant(ConstantInfo info) { + throw new StateError('should not diff ConstantInfo'); + } + + @override + visitLibrary(LibraryInfo info) { + var other = _other as LibraryInfo; + _checkSize(info, other); + _diffList(info.topLevelVariables, other.topLevelVariables); + _diffList(info.topLevelFunctions, other.topLevelFunctions); + _diffList(info.classes, other.classes); + } + + @override + visitClass(ClassInfo info) { + var other = _other as ClassInfo; + _checkSize(info, other); + _checkDeferredStatus(info, other); + _diffList(info.fields, other.fields); + _diffList(info.functions, other.functions); + } + + @override + visitClosure(ClosureInfo info) { + var other = _other as ClosureInfo; + _checkSize(info, other); + _checkDeferredStatus(info, other); + _diffList([info.function], [other.function]); + } + + @override + visitField(FieldInfo info) { + var other = _other as FieldInfo; + _checkSize(info, other); + _checkDeferredStatus(info, other); + _diffList(info.closures, other.closures); + } + + @override + visitFunction(FunctionInfo info) { + var other = _other as FunctionInfo; + _checkSize(info, other); + _checkDeferredStatus(info, other); + _diffList(info.closures, other.closures); + } + + @override + visitTypedef(TypedefInfo info) { + var other = _other as TypedefInfo; + _checkSize(info, other); + _checkDeferredStatus(info, other); + } + + void _checkSize(BasicInfo info, BasicInfo other) { + if (info.size != other.size) { + diffs.add(new SizeDiff(info, other.size - info.size)); + } + } + + void _checkDeferredStatus(BasicInfo oldInfo, BasicInfo newInfo) { + var oldIsDeferred = _isDeferred(oldInfo); + var newIsDeferred = _isDeferred(newInfo); + if (oldIsDeferred != newIsDeferred) { + diffs.add(new DeferredStatusDiff(oldInfo, oldIsDeferred)); + } + } + + bool _isDeferred(BasicInfo info) { + var outputUnit = info.outputUnit; + return outputUnit.name != null && + outputUnit.name.isNotEmpty && + outputUnit.name != 'main'; + } + + void _diffList(List oldInfos, List newInfos) { + var oldNames = {}; + var newNames = {}; + for (var oldInfo in oldInfos) { + oldNames[longName(oldInfo, useLibraryUri: true)] = oldInfo; + } + for (var newInfo in newInfos) { + newNames[longName(newInfo, useLibraryUri: true)] = newInfo; + } + for (var oldName in oldNames.keys) { + if (newNames[oldName] == null) { + diffs.add(new RemoveDiff(oldNames[oldName])); + } else { + _other = newNames[oldName]; + oldNames[oldName].accept(this); + } + } + for (var newName in newNames.keys) { + if (oldNames[newName] == null) { + diffs.add(new AddDiff(newNames[newName])); + } + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 04d02c3d6f9cf..399df6c5637ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: dart2js_info -version: 0.5.4+2 +version: 0.5.5 description: > Libraries and tools to process data produced when running dart2js with --dump-info. @@ -23,6 +23,7 @@ executables: dart2js_info_code_deps: code_deps dart2js_info_coverage_log_server: coverage_log_server dart2js_info_debug_info: debug_info + dart2js_info_diff: diff dart2js_info_deferred_library_check: deferred_library_check dart2js_info_deferred_library_size: deferred_library_size dart2js_info_deferred_library_layout: deferred_library_layout