| // 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:io'; |
| import 'dart:math' show max; |
| |
| import 'package:args/command_runner.dart'; |
| |
| import 'package:dart2js_info/info.dart'; |
| import 'package:dart2js_info/src/io.dart'; |
| import 'package:yaml/yaml.dart'; |
| |
| import 'usage_exception.dart'; |
| |
| /// Command presenting how much each library contributes to the total code. |
| class LibrarySizeCommand extends Command<void> with PrintUsageException { |
| @override |
| final String name = "library_size"; |
| @override |
| final String description = "See breakdown of code size by library."; |
| |
| LibrarySizeCommand() { |
| argParser.addOption('grouping', |
| help: 'YAML file specifying how libraries should be grouped.'); |
| } |
| |
| @override |
| void run() async { |
| final argRes = argResults!; |
| final args = argRes.rest; |
| if (args.isEmpty) { |
| usageException('Missing argument: info.data'); |
| } |
| |
| final info = await infoFromFile(args.first); |
| |
| final groupingFile = argRes['grouping']; |
| final groupingText = groupingFile != null |
| ? File(groupingFile).readAsStringSync() |
| : defaultGrouping; |
| final groupingYaml = loadYaml(groupingText); |
| final groups = []; |
| for (var group in groupingYaml['groups']) { |
| groups.add(_Group( |
| group['name'], RegExp(group['regexp']), group['cluster'] ?? 0)); |
| } |
| |
| final sizes = {}; |
| var allLibs = 0; |
| for (LibraryInfo lib in info.libraries) { |
| allLibs += lib.size; |
| for (var group in groups) { |
| final match = group.matcher.firstMatch('${lib.uri}'); |
| if (match != null) { |
| var name = group.name; |
| if (name == null && match.groupCount > 0) name = match.group(1); |
| name ??= match.group(0); |
| sizes.putIfAbsent(name, () => _SizeEntry(name, group.cluster)); |
| sizes[name].size += lib.size; |
| } |
| } |
| } |
| |
| var allConstants = 0; |
| for (var constant in info.constants) { |
| allConstants += constant.size; |
| } |
| |
| final all = sizes.keys.toList(); |
| all.sort((a, b) => sizes[a].compareTo(sizes[b])); |
| final realTotal = info.program!.size; |
| var longest = 0; |
| final rows = <_Row>[]; |
| addRow(String label, int value) { |
| rows.add(_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) { |
| final entry = sizes[name]; |
| if (lastCluster < entry.cluster) { |
| rows.add(const _Divider()); |
| lastCluster = entry.cluster; |
| } |
| final 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}) { |
| final 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}/(.*)" } |
| """; |