blob: 810f50b235f2603da99dfc4a41d997c9c48b3282 [file] [log] [blame]
// 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:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:yaml/yaml.dart';
main(args) async {
if (args.length < 1) {
print('usage: dart tool/library_size_split.dart '
'path-to-info.json [grouping.yaml]');
exit(1);
}
var info = await infoFromFile(args.first);
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 = <_Row>[];
_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}/(.*)" }
""";