| // Copyright (c) 2015, 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. |
| |
| /// Command-line tool to show the size distribution of generated code among |
| /// libraries. Libraries can be grouped using regular expressions. You can |
| /// specify what regular expressions to use by providing a `grouping.yaml` file. |
| /// The format of the `grouping.yaml` file is as follows: |
| /// ```yaml |
| /// groups: |
| /// - { regexp: "package:(foo)/*.dart", name: "group name 1", cluster: 2} |
| /// - { regexp: "dart:.*", name: "group name 2", cluster: 3} |
| /// ``` |
| /// The file should include a single key `groups` containing a list of group |
| /// specifications. Each group is specified by a map of 3 entries: |
| /// |
| /// * regexp (required): a regexp used to match entries that belong to the |
| /// group. |
| /// |
| /// * name (optional): the name given to this group in the output table. If |
| /// omitted, the name is derived from the regexp as the match's group(1) or |
| /// group(0) if no group was defined. When names are omitted the group |
| /// specification implicitly defines several groups, one per observed name. |
| /// |
| /// * cluster (optional): a clustering index for how data is shown in a table. |
| /// Groups with higher cluster indices are shown later in the table after a |
| /// dividing line. If missing, the cluster index defaults to 0. |
| /// |
| /// Here is an example configuration, with comments about what each entry does: |
| /// |
| /// ```yaml |
| /// groups: |
| /// # This group shows the total size for all libraries that were loaded from |
| /// # file:// urls, it is shown in cluster #2, which happens to be the last |
| /// # cluster in this example before the totals are shown: |
| /// - { name: "Loose files", regexp: "file://.*", cluster: 2} |
| /// |
| /// # This group shows the total size of all code loaded from packages: |
| /// - { name: "All packages", regexp: "package:.*", cluster: 2} |
| /// |
| /// # This group shows the total size of all code loaded from core libraries: |
| /// - { name: "Core libs", regexp: "dart:.*", cluster: 2} |
| /// |
| /// # This group shows the total size of all libraries in a single package. Here |
| /// # we omitted the `name` entry, instead we extract it from the regexp |
| /// # directly. In this case, the name will be the package-name portion of the |
| /// # package-url (determined by group(1) of the regexp). |
| /// - { regexp: "package:([^/]*)", cluster: 1} |
| /// |
| /// # The next two groups match the entire library url as the name of the group. |
| /// - regexp: "package:.*" |
| /// - regexp: "dart:.*" |
| /// |
| /// # If your code lives under /my/project/dir, this will match any file loaded |
| /// from a file:// url, and we use as a name the relative path to it. |
| /// - regexp: "file:///my/project/dir/(.*)" |
| ///``` |
| /// |
| /// 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:yaml/yaml.dart'; |
| |
| main(args) { |
| 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 groupingText = |
| args.length > 1 ? new File(args[1]).readAsStringSync() : defaultGrouping; |
| var groupingYaml = loadYaml(groupingText); |
| var groups = []; |
| for (var group in groupingYaml['groups']) { |
| groups.add(new _Group( |
| group['name'], new RegExp(group['regexp']), group['cluster'] ?? 0)); |
| } |
| |
| var sizes = {}; |
| var allLibs = 0; |
| for (LibraryInfo lib in info.libraries) { |
| allLibs += lib.size; |
| groups.forEach((group) { |
| var match = group.matcher.firstMatch('${lib.uri}'); |
| if (match != null) { |
| var name = group.name; |
| if (name == null && match.groupCount > 0) name = match.group(1); |
| if (name == null) name = match.group(0); |
| sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster)); |
| sizes[name].size += lib.size; |
| } |
| }); |
| } |
| |
| var allConstants = 0; |
| for (var constant in info.constants) { |
| allConstants += constant.size; |
| } |
| |
| var all = sizes.keys.toList(); |
| all.sort((a, b) => sizes[a].compareTo(sizes[b])); |
| var realTotal = info.program.size; |
| var longest = 0; |
| var rows = []; |
| _addRow(String label, int value) { |
| rows.add(new _Row(label, value)); |
| longest = max(longest, label.length); |
| } |
| |
| _printRow(_Row row) { |
| if (row is _Divider) { |
| print(' ' + ('-' * (longest + 18))); |
| return; |
| } |
| |
| var percent = row.value == realTotal |
| ? '100' |
| : (row.value * 100 / realTotal).toStringAsFixed(2); |
| print(' ${_pad(row.label, longest + 1, right: true)}' |
| ' ${_pad(row.value, 8)} ${_pad(percent, 6)}%'); |
| } |
| |
| var lastCluster = 0; |
| for (var name in all) { |
| var entry = sizes[name]; |
| if (lastCluster < entry.cluster) { |
| rows.add(const _Divider()); |
| lastCluster = entry.cluster; |
| } |
| var size = entry.size; |
| _addRow(name, size); |
| } |
| rows.add(const _Divider()); |
| _addRow("All libraries (excludes preambles, statics & consts)", allLibs); |
| _addRow("Shared consts", allConstants); |
| _addRow("Total accounted", allLibs + allConstants); |
| _addRow("Program Size", realTotal); |
| rows.forEach(_printRow); |
| } |
| |
| /// A group defined in the configuration. |
| class _Group { |
| /// Name of the group. May be null if the name is derived from the matcher. In |
| /// that case, the name would be group(1) of the matched expression if it |
| /// exist, or group(0) otherwise. |
| final String name; |
| |
| /// Regular expression matching members of the group. |
| final RegExp matcher; |
| |
| /// Index used to cluster groups together. Useful when the grouping |
| /// configuration describes some coarser groups than orders (e.g. summary of |
| /// packages would be in a different cluster than a summary of libraries). |
| final int cluster; |
| |
| _Group(this.name, this.matcher, this.cluster); |
| } |
| |
| class _SizeEntry { |
| final String name; |
| final int cluster; |
| int size = 0; |
| |
| _SizeEntry(this.name, this.cluster); |
| |
| int compareTo(_SizeEntry other) => |
| cluster == other.cluster ? size - other.size : cluster - other.cluster; |
| } |
| |
| class _Row { |
| final String label; |
| final int value; |
| const _Row(this.label, this.value); |
| } |
| |
| class _Divider extends _Row { |
| const _Divider() : super('', 0); |
| } |
| |
| _pad(value, n, {bool right: false}) { |
| var s = '$value'; |
| if (s.length >= n) return s; |
| var pad = ' ' * (n - s.length); |
| return right ? '$s$pad' : '$pad$s'; |
| } |
| |
| /// Default grouping specification that includes an entry per library, and |
| /// grouping entries for each package, all packages, all core libs, and loose |
| /// files. |
| final defaultGrouping = """ |
| groups: |
| - { name: "Loose files", regexp: "file://.*", cluster: 2} |
| - { name: "All packages", regexp: "package:.*", cluster: 2} |
| - { name: "Core libs", regexp: "dart:.*", cluster: 2} |
| # We omitted `name` to extract the group name from the regexp directly. |
| # Here the name is the name of the package: |
| - { regexp: "package:([^/]*)", cluster: 1} |
| # Here the name is the url of the package and dart core libraries: |
| - { regexp: "package:.*"} |
| - { regexp: "dart:.*"} |
| # Here the name is the relative path of loose files: |
| - { regexp: "file://${Directory.current.path}/(.*)" } |
| """; |