blob: b0c2c1b373f3fc363012e588f2b72e53724de1e8 [file] [log] [blame]
// Copyright (c) 2017, 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.
/*
Usage:
$ tools/test.py -m release \
-c dart2js -r d8 --dart2js-batch --report \
--host-checked \
--dart2js_options="--library-root=out/ReleaseX64/dart-sdk/ --use-kernel" \
language language_2 corelib corelib_2 \
dart2js_native dart2js_extra \
2>&1 > LOG
$ sdk/bin/dart pkg/compiler/tool/status_files/rank_stacks.dart LOG > STACKS.txt
*/
import 'dart:io';
import 'package:args/args.dart';
import 'log_parser.dart';
import 'record.dart';
int stackPrintLength;
int howManyStacks;
void die(String why) {
print(why);
print('Usage:\n'
'dart rank_stacks.dart [options] test-logs\n\n'
'${argParser.usage}');
exit(1);
}
ArgParser argParser = new ArgParser()
..addOption('stacks',
abbr: 's',
defaultsTo: '0',
help: 'Number of highest ranking stacks to print (0 for all).')
..addOption('length',
abbr: 'l', defaultsTo: '12', help: 'Number of stack frames printed.');
int intOption(ArgResults args, String name) {
onError(String text) {
die("Value '$text' is not an integer. "
"Option '$name' requires an integer value.");
}
return int.parse(args[name], onError: onError);
}
main(args) {
List<String> rest;
try {
var argResults = argParser.parse(args);
howManyStacks = intOption(argResults, 'stacks');
stackPrintLength = intOption(argResults, 'length');
rest = argResults.rest;
} catch (e) {
die('$e');
}
if (rest.isEmpty) die('No input file.');
var records = <Record>[];
for (String input in rest) {
var uri = Uri.base.resolve(input);
var file = new File.fromUri(uri);
if (!file.existsSync()) {
die("File not found: '$input'.");
}
String text = file.readAsStringSync();
records.addAll(parse(text));
}
var trie = new TrieNode(null, 0);
for (var record in records) {
enter(record, 0, trie);
}
var leaves = trieLeaves(trie).toList();
leaves.sort(compareNodesByCountAndStack);
for (var leaf in howManyStacks == 0 ? leaves : leaves.take(howManyStacks)) {
print('');
var examples = leaf.members.map(fullReasonOf).toSet().toList();
examples.sort();
print('${leaf.length} of:');
for (var example in examples) {
var count = leaf.members.where((r) => fullReasonOf(r) == example).length;
var countAligned = '$count'.padLeft(6);
if (examples.length == 1) countAligned = ' .';
var indentedExample = '\t' + example.replaceAll('\n', '\n\t');
print('${countAligned}${indentedExample}');
}
for (var line in leaf.members.first.stack.take(stackPrintLength)) {
print(' $line');
}
}
}
String fullReasonOf(Record r) {
// Some records have no matched reason, so default to test status.
return r.fullReason ?? r.actual;
}
int compareNodesByCountAndStack(TrieNode a, TrieNode b) {
int r = b.length.compareTo(a.length);
if (r != 0) return r;
List<String> stackA = a.members.first.stack;
List<String> stackB = b.members.first.stack;
int lengthA = stackA.length;
int lengthB = stackB.length;
for (int i = 0; i < lengthA && i < lengthB; i++) {
r = stackA[i].compareTo(stackB[i]);
if (r != 0) return r;
}
return lengthA.compareTo(lengthB);
}
class TrieNode {
final int depth;
final String key;
final Map<String, TrieNode> map = <String, TrieNode>{};
final List<Record> members = <Record>[];
int get length => members.length;
TrieNode(this.key, this.depth);
@override
String toString() => 'TrieNode(#$length)';
}
void enter(Record record, int depth, TrieNode root) {
// Cluster on printed stack.
if (depth >= stackPrintLength || depth >= record.stack.length) {
root.members.add(record);
return;
}
var key = record.stack[depth];
var node = root.map[key] ??= new TrieNode(key, depth + 1);
enter(record, depth + 1, node);
}
void printTrie(TrieNode node) {
var indent = ' ' * node.depth;
print('${indent} ${node.length} ${node.key}');
for (var key in node.map.keys) {
printTrie(node.map[key]);
}
}
trieLeaves(node) sync* {
if (node.members.isNotEmpty) {
yield node;
}
for (var v in node.map.values) {
yield* trieLeaves(v);
}
}