blob: 2a4d1353a43b605a605fee9bebb0b2bbab3c3acb [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 presenting combined information from dump-info and
/// coverage data.
///
/// This tool requires two input files an `.info.json` and a
/// `.coverage.json` file. To produce these files you need to follow these
/// steps:
///
/// * Compile an app with dart2js using --dump-info and defining the
/// Dart environment `traceCalls=post`:
///
/// DART_VM_OPTIONS="-DtraceCalls=post" dart2js --dump-info main.dart
///
/// Because coverage/tracing data is currently experimental, the feature is
/// not exposed as a flag in dart2js, but you can enable it using the Dart
/// environment flag. The flag only works dart2js version 1.13.0 or newer.
///
/// * Launch the coverage server tool (in this package) to serve up the
/// Javascript code in your app:
///
/// dart tool/coverage_log_server.dart main.dart.js
///
/// * (optional) If you have a complex application setup, integrate your
/// application server to proxy to the log server any GET request for the
/// .dart.js file and /coverage POST requests that send coverage data.
///
/// * Load your app and use it to exercise the entire code.
///
/// * Shut down the coverage server (Ctrl-C)
///
/// * Finally, run this tool.
library compiler.tool.live_code_size_analysis;
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:dart2js_info/info.dart';
import 'package:dart2js_info/src/io.dart';
import 'package:dart2js_info/src/util.dart';
import 'function_size_analysis.dart';
import 'usage_exception.dart';
class LiveCodeAnalysisCommand extends Command<void> with PrintUsageException {
@override
final String name = "coverage_analysis";
@override
final String description = "Analyze coverage data collected via the"
" 'coverage_server' command";
LiveCodeAnalysisCommand() {
argParser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Show verbose details.');
}
@override
void run() async {
final argRes = argResults!;
var args = argRes.rest;
if (args.length < 2) {
usageException('Missing arguments, expected: info.data coverage.json');
}
await _liveCodeAnalysis(args[0], args[1], argRes['verbose']);
}
}
_liveCodeAnalysis(infoFile, coverageFile, bool verbose) async {
var info = await infoFromFile(infoFile);
var coverage = jsonDecode(File(coverageFile).readAsStringSync());
int realTotal = info.program!.size;
int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size);
int totalCode = 0;
int reachableCode = 0;
List<Info> unused = [];
void tallyCode(Info f) {
totalCode += f.size;
var data = coverage[f.coverageId];
if (data != null) {
// Validate that the name match, it might not match if using a different
// version of the app.
// TODO(sigmund): use the same name.
// TODO(sigmund): inject a time-stamp in the code and dumpinfo and
// validate just once.
var name = f.name;
if (name.contains('.')) name = name.substring(name.lastIndexOf('.') + 1);
var otherName = data['name'];
if (otherName.contains('.')) {
otherName = otherName.substring(otherName.lastIndexOf('.') + 1);
}
if (otherName != name && otherName != '') {
print('invalid coverage: $data for $f, ($name vs $otherName)');
}
reachableCode += f.size;
} else {
// we should track more precisely data about inlined functions
unused.add(f);
}
}
info.functions.forEach(tallyCode);
info.fields.forEach(tallyCode);
_showHeader('', 'bytes', '%');
_show('Program size', realTotal, realTotal);
_show('Libraries (excluding statics)', totalLib, realTotal);
_show('Code (functions + fields)', totalCode, realTotal);
_show('Reachable code', reachableCode, realTotal);
print('');
_showHeader('', 'count', '%');
var total = info.functions.length + info.fields.length;
_show('Functions + fields', total, total);
_show('Reachable', total - unused.length, total);
// TODO(sigmund): support grouping results by package.
if (verbose) {
print('\nDistribution of code that was not used when running the app:');
showCodeDistribution(info,
filter: (f) => !coverage.containsKey(f.coverageId) && f.size > 0,
showLibrarySizes: true);
} else {
print('\nUse `-v` to see details about the size of unreachable code');
}
}
_showHeader(String msg, String header1, String header2) {
print(' ${pad(msg, 30, right: true)} ${pad(header1, 8)} ${pad(header2, 6)}');
}
_show(String msg, int size, int total) {
var percent = (size * 100 / total).toStringAsFixed(2);
print(' ${pad(msg, 30, right: true)} ${pad(size, 8)} ${pad(percent, 6)}%');
}