blob: 04aa819f4d3ac5d32792c70393dffb5ab05d24e3 [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.
/// Tool presenting how much each function contributes to the total code.
library compiler.tool.function_size_analysis;
import 'dart:math' as math;
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/graph.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'usage_exception.dart';
/// Command presenting how much each function contributes to the total code.
class FunctionSizeCommand extends Command<void> with PrintUsageException {
@override
final String name = "function_size";
@override
final String description = "See breakdown of code size by function.";
@override
void run() async {
final args = argResults!.rest;
if (args.isEmpty) {
usageException('Missing argument: info.data');
}
final info = await infoFromFile(args.first);
showCodeDistribution(info);
}
}
showCodeDistribution(AllInfo info,
{bool Function(Info info)? filter, bool showLibrarySizes = false}) {
var realTotal = info.program!.size;
filter ??= (i) => true;
var reported = <BasicInfo>[
...info.functions.where(filter),
...info.fields.where(filter)
];
// Compute a graph from the dependencies in [info].
Graph<Info> graph = graphFromInfo(info);
// Compute the strongly connected components and calculate their size.
var components = graph.computeTopologicalSort();
print('total elements: ${graph.nodes.length}');
print('total strongly connected components: ${components.length}');
var maxS = 0;
var totalCount = graph.nodeCount;
var minS = totalCount;
var nodeData = {};
for (var scc in components) {
var sccData = _SccData();
maxS = math.max(maxS, scc.length);
minS = math.min(minS, scc.length);
for (var f in scc) {
sccData.size += f.size;
for (var g in graph.targetsOf(f)) {
var gData = nodeData[g];
if (gData != null) sccData.deps.add(gData);
}
}
for (var f in scc) {
nodeData[f] = sccData;
}
}
print('scc sizes: min: $minS, max: $maxS, '
'avg ${totalCount / components.length}');
// Compute a dominator tree and calculate the size dominated by each element.
// TODO(sigmund): we need a more reliable way to fetch main.
var mainMethod = info.functions.firstWhere((f) => f.name == 'main');
var dominatorTree = graph.dominatorTree(mainMethod);
var dominatedSize = {};
int helper(n) {
int size = n.size;
assert(!dominatedSize.containsKey(n));
dominatedSize[n] = 0;
dominatorTree.targetsOf(n).forEach((m) {
size += helper(m);
});
dominatedSize[n] = size;
return size;
}
helper(mainMethod);
for (var n in reported) {
dominatedSize.putIfAbsent(n, () => n.size);
}
reported.sort((a, b) =>
(dominatedSize[b] + nodeData[b].maxSize) -
(dominatedSize[a] + nodeData[a].maxSize));
if (showLibrarySizes) {
print(' --- Results per library ---');
var totals = <LibraryInfo, int>{};
var longest = 0;
for (var info in reported) {
var size = info.size;
Info? lib = info;
while (lib != null && lib is! LibraryInfo) {
lib = lib.parent;
}
if (lib == null) return;
totals.update(lib as LibraryInfo, (value) => value + size,
ifAbsent: () => size);
longest = math.max(longest, '${lib.uri}'.length);
}
_showLibHeader(longest + 1);
var reportedByLibrary = totals.keys.toList();
reportedByLibrary.sort((a, b) => totals[b]! - totals[a]!);
for (final info in reportedByLibrary) {
_showLib('${info.uri}', totals[info]!, realTotal, longest + 1);
}
}
print('\n --- Results per element (field or function) ---');
_showElementHeader();
for (var info in reported) {
var size = info.size;
var min = dominatedSize[info];
var max = nodeData[info].maxSize;
_showElement(
longName(info, useLibraryUri: true), size, min, max, realTotal);
}
}
/// Data associated with an SCC. Used to compute the reachable code size.
class _SccData {
int size = 0;
Set<_SccData> deps = {};
_SccData();
late final int maxSize = computeMaxSize();
int computeMaxSize() {
var max = 0;
var seen = <_SccData>{};
void helper(_SccData n) {
if (!seen.add(n)) return;
max += n.size;
n.deps.forEach(helper);
}
helper(this);
return max;
}
}
_showLibHeader(int namePadding) {
print(' ${pad("Library", namePadding, right: true)}'
' ${pad("bytes", 8)} ${pad("%", 6)}');
}
_showLib(String msg, int size, int total, int namePadding) {
var percent = (size * 100 / total).toStringAsFixed(2);
print(' ${pad(msg, namePadding, right: true)}'
' ${pad(size, 8)} ${pad(percent, 6)}%');
}
_showElementHeader() {
print('${pad("element size", 16)} '
'${pad("dominated size", 18)} '
'${pad("reachable size", 18)} '
'Element identifier');
}
_showElement(String name, int size, int dominatedSize, int maxSize, int total) {
var percent = (size * 100 / total).toStringAsFixed(2);
var minPercent = (dominatedSize * 100 / total).toStringAsFixed(2);
var maxPercent = (maxSize * 100 / total).toStringAsFixed(2);
print('${pad(size, 8)} ${pad(percent, 6)}% '
'${pad(dominatedSize, 10)} ${pad(minPercent, 6)}% '
'${pad(maxSize, 10)} ${pad(maxPercent, 6)}% '
'$name');
}