add tool to get breakdown of deferred libraries by size

R=sigmund@google.com

Review URL: https://codereview.chromium.org//2201903004 .
diff --git a/.gitignore b/.gitignore
index cec5b50..cb088b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 .buildlog
 .DS_Store
 .idea
+*.iml
 .pub/
 .settings/
 build/
diff --git a/README.md b/README.md
index 667ac96..e04a96b 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,10 @@
     was split into deferred parts as expected. This tool takes a specification
     of the expected layout of code into deferred parts, and checks that the
     output from `dart2js` meets the specification.
+    
+  * [`deferred_library_size`][deferred_size]: a tool that gives a breakdown of
+    the sizes of the deferred parts of the program. This can show how much of
+    your total code size can be loaded deferred.
 
   * [`function_size_analysis`][function_analysis]: a tool that shows how much
     code was attributed to each function. This tool also uses dependency
@@ -240,6 +244,32 @@
 instance, if you have `import 'package:foo/bar.dart' deferred as baz;` in your
 dart file, then the corresponding name in the specification file is 'baz'.
 
+### Deferred library size tool
+
+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
+pub global activate dart2js_info # only needed once
+dart2js_info_deferred_library_size out.js.info.json
+```
+
+The tool will output a table listing all of the deferred imports in the program
+as well as the "main" chunk, which is not deferred. The output looks like:
+
+```
+Size by library
+------------------------------------------------
+main                                    12345678
+foo                                      7654321
+bar                                      1234567
+------------------------------------------------
+Main chunk size                         12345678
+Deferred code size                       8888888
+Percent of code deferred                  41.86%
+```
+
 ### Function size analysis tool
 
 This command-line tool presents how much each function contributes to the total
@@ -321,6 +351,7 @@
 [code_deps]: https://github.com/dart-lang/dart2js_info/blob/master/bin/code_deps.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
 [coverage]: https://github.com/dart-lang/dart2js_info/blob/master/bin/coverage_log_server.dart
 [live]: https://github.com/dart-lang/dart2js_info/blob/master/bin/live_code_size_analysis.dart
 [function_analysis]: https://github.com/dart-lang/dart2js_info/blob/master/bin/function_size_analysis.dart
diff --git a/bin/code_deps.dart b/bin/code_deps.dart
index 9899e8d..a90df78 100644
--- a/bin/code_deps.dart
+++ b/bin/code_deps.dart
@@ -26,14 +26,13 @@
 library dart2js_info.bin.code_deps;
 
 import 'dart:collection';
-import 'dart:convert';
 import 'dart:io';
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) {
+main(args) async {
   if (args.length < 2) {
     print('usage: dart2js_info_code_deps path-to.info.json <query>');
     print('   where <query> can be:');
@@ -42,14 +41,7 @@
     exit(1);
   }
 
-  var json;
-  try {
-    json = JSON.decode(new File(args[0]).readAsStringSync());
-  } catch (e) {
-    print('error: could not read ${args[0]}');
-    exit(1);
-  }
-  var info = new AllInfoJsonCodec().decode(json);
+  var info = await infoFromFile(args.first);
   var graph = graphFromInfo(info);
 
   var queryName = args[1];
diff --git a/bin/coverage_log_server.dart b/bin/coverage_log_server.dart
index c19ae7a..cbab8c6 100644
--- a/bin/coverage_log_server.dart
+++ b/bin/coverage_log_server.dart
@@ -136,7 +136,7 @@
     }
 
     // Handle POST requests to record coverage data, and GET requests to display
-    // the currently coverage resutls.
+    // the currently coverage results.
     if (urlPath == _expectedPath('coverage')) {
       if (request.method == 'GET') {
         return new shelf.Response.ok(_serializedData, headers: TEXT_HEADERS);
diff --git a/bin/debug_info.dart b/bin/debug_info.dart
index f0dd47b..3c09114 100644
--- a/bin/debug_info.dart
+++ b/bin/debug_info.dart
@@ -6,23 +6,20 @@
 /// that it is consistent and that it covers all the data we expect it to cover.
 library dart2js_info.bin.debug_info;
 
-import 'dart:convert';
 import 'dart:io';
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) {
+main(args) async {
   if (args.length < 1) {
     print('usage: dart tool/debug_info.dart path-to-info.json '
         '[--show-library libname]');
     exit(1);
   }
 
-  var filename = args[0];
-  var json = JSON.decode(new File(filename).readAsStringSync());
-  var info = new AllInfoJsonCodec().decode(json);
+  var info = await infoFromFile(args.first);
   var debugLibName;
 
   if (args.length > 2 && args[1] == '--show-library') {
diff --git a/bin/deferred_library_check.dart b/bin/deferred_library_check.dart
index d9cd151..19cceef 100644
--- a/bin/deferred_library_check.dart
+++ b/bin/deferred_library_check.dart
@@ -36,11 +36,10 @@
 library dart2js_info.bin.deferred_library_check;
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
-import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/deferred_library_check.dart';
+import 'package:dart2js_info/src/util.dart';
 import 'package:yaml/yaml.dart';
 
 Future main(List<String> args) async {
@@ -56,11 +55,6 @@
   if (failures.isNotEmpty) exitCode = 1;
 }
 
-Future<AllInfo> infoFromFile(String fileName) async {
-  var file = await new File(fileName).readAsString();
-  return new AllInfoJsonCodec().decode(JSON.decode(file));
-}
-
 Future manifestFromFile(String fileName) async {
   var file = await new File(fileName).readAsString();
   return loadYaml(file);
diff --git a/bin/deferred_library_size.dart b/bin/deferred_library_size.dart
new file mode 100644
index 0000000..349459c
--- /dev/null
+++ b/bin/deferred_library_size.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2016, 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 gives a breakdown of code size by deferred part in the program.
+library dart2js_info.bin.deferred_library_size;
+
+import 'dart:math';
+
+import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/src/util.dart';
+
+main(args) async {
+  // TODO(het): Would be faster to only parse the 'outputUnits' part
+  var info = await infoFromFile(args.first);
+  var sizeByImport = getSizeByImport(info);
+  printSizes(sizeByImport, info.program.size);
+}
+
+class ImportSize {
+  final String import;
+  final int size;
+
+  const ImportSize(this.import, this.size);
+
+  String toString() {
+    return '$import: $size';
+  }
+}
+
+void printSizes(Map<String, int> sizeByImport, int programSize) {
+  var importSizes = <ImportSize>[];
+  sizeByImport.forEach((import, size) {
+    importSizes.add(new ImportSize(import, size));
+  });
+  // Sort by size, largest first.
+  importSizes.sort((a, b) => b.size - a.size);
+  var longest = importSizes.fold('Percent of code deferred'.length,
+      (longest, importSize) => max(longest, importSize.import.length));
+
+  _printRow(label, data, {int width: 15}) {
+    print('${label.toString().padRight(longest + 1)}'
+        '${data.toString().padLeft(width)}');
+  }
+
+  print('');
+  print('Size by library');
+  print('-' * (longest + 16));
+  for (var importSize in importSizes) {
+    // TODO(het): split into specific and shared size
+    _printRow(importSize.import, importSize.size);
+  }
+  print('-' * (longest + 16));
+
+  var mainChunkSize = sizeByImport['main'];
+  var deferredSize = programSize - mainChunkSize;
+  var percentDeferred = (deferredSize * 100 / programSize).toStringAsFixed(2);
+  _printRow('Main chunk size', mainChunkSize);
+  _printRow('Deferred code size', deferredSize);
+  _printRow('Percent of code deferred', '$percentDeferred%');
+}
+
+Map<String, int> getSizeByImport(AllInfo info) {
+  var sizeByImport = <String, int>{};
+  for (var outputUnit in info.outputUnits) {
+    if (outputUnit.name == 'main' || outputUnit.name == null) {
+      sizeByImport['main'] = outputUnit.size;
+    } else {
+      for (var import in outputUnit.imports) {
+        sizeByImport.putIfAbsent(import, () => 0);
+        sizeByImport[import] += outputUnit.size;
+      }
+    }
+  }
+  return sizeByImport;
+}
diff --git a/bin/function_size_analysis.dart b/bin/function_size_analysis.dart
index 3606310..e97166d 100644
--- a/bin/function_size_analysis.dart
+++ b/bin/function_size_analysis.dart
@@ -6,17 +6,14 @@
 /// code.
 library compiler.tool.live_code_size_analysis;
 
-import 'dart:convert';
-import 'dart:io';
 import 'dart:math' as math;
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) {
-  var json = JSON.decode(new File(args[0]).readAsStringSync());
-  var info = new AllInfoJsonCodec().decode(json);
+main(args) async {
+  var info = await infoFromFile(args.first);
   showCodeDistribution(info);
 }
 
diff --git a/bin/library_size_split.dart b/bin/library_size_split.dart
index 8dd419a..9221bf4 100644
--- a/bin/library_size_split.dart
+++ b/bin/library_size_split.dart
@@ -59,23 +59,21 @@
 /// This example is very similar to [defaultGrouping].
 library dart2js_info.bin.library_size_split;
 
-import 'dart:convert';
 import 'dart:io';
 import 'dart:math' show max;
 
 import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/src/util.dart';
 import 'package:yaml/yaml.dart';
 
-main(args) {
+main(args) async {
   if (args.length < 1) {
     print('usage: dart tool/library_size_split.dart '
         'path-to-info.json [grouping.yaml]');
     exit(1);
   }
 
-  var filename = args[0];
-  var json = JSON.decode(new File(filename).readAsStringSync());
-  var info = new AllInfoJsonCodec().decode(json);
+  var info = await infoFromFile(args.first);
 
   var groupingText =
       args.length > 1 ? new File(args[1]).readAsStringSync() : defaultGrouping;
diff --git a/bin/live_code_size_analysis.dart b/bin/live_code_size_analysis.dart
index 333f5c4..5520ff1 100644
--- a/bin/live_code_size_analysis.dart
+++ b/bin/live_code_size_analysis.dart
@@ -39,17 +39,17 @@
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/util.dart';
+
 import 'function_size_analysis.dart';
 
-main(args) {
+main(args) async {
   if (args.length < 2) {
     print('usage: dart tool/live_code_size_analysis.dart path-to-info.json '
         'path-to-coverage.json [-v]');
     exit(1);
   }
 
-  var json = JSON.decode(new File(args[0]).readAsStringSync());
-  var info = new AllInfoJsonCodec().decode(json);
+  var info = await infoFromFile(args.first);
   var coverage = JSON.decode(new File(args[1]).readAsStringSync());
   var verbose = args.length > 2 && args[2] == '-v';
 
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 1b8a084..b00f434 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -4,7 +4,12 @@
 
 library dart2js_info.src.util;
 
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
 import 'package:dart2js_info/info.dart';
+
 import 'graph.dart';
 
 /// Computes a graph of dependencies from [info].
@@ -124,3 +129,8 @@
   helper(metric);
   return sb.toString();
 }
+
+Future<AllInfo> infoFromFile(String fileName) async {
+  var file = await new File(fileName).readAsString();
+  return new AllInfoJsonCodec().decode(JSON.decode(file));
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1a43066..0ea5855 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart2js_info
-version: 0.2.5
+version: 0.2.6
 description: >
   Libraries and tools to process data produced when running dart2js with
   --dump-info.
@@ -24,6 +24,7 @@
   dart2js_info_coverage_log_server:     coverage_log_server
   dart2js_info_debug_info:              debug_info
   dart2js_info_deferred_library_check:  deferred_library_check
+  dart2js_info_deferred_library_size:   deferred_library_size
   dart2js_info_function_size_analysis:  function_size_analysis
   dart2js_info_library_size_split:      library_size_split
   dart2js_info_live_code_size_analysis: live_code_size_analysis
diff --git a/tool/travis.sh b/tool/travis.sh
index ca28b04..e0a8098 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -9,7 +9,7 @@
 
 # Verify that the libraries are error free.
 dartanalyzer --fatal-warnings \
-  lib/info.dart \
+  lib/**/*.dart \
   test/*.dart
 
 # Run the tests.