Chromium Code Reviews| Index: pkg/compiler/lib/src/stats/stats.dart |
| diff --git a/pkg/compiler/lib/src/stats/stats.dart b/pkg/compiler/lib/src/stats/stats.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b95b156a9252aae14bf6af6b8ebec7d6181f3a2c |
| --- /dev/null |
| +++ b/pkg/compiler/lib/src/stats/stats.dart |
| @@ -0,0 +1,464 @@ |
| +// 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. |
| + |
| +/// Collects information used to debug and analyze internal parts of the |
| +/// compiler. |
| +/// |
| +/// Currently this is focused mainly on data from types and inference, such as |
| +/// understanding of types of expressions and precision of send operations. |
| +/// |
| +/// This library focuses on representing the information itself. |
| +/// `stats_builder.dart` contains visitors we use to collect the data, while |
| +/// `tools/stats/server.dart` contains logic to visualize the data. |
| +library stats; |
| + |
| +/// All results from a single run of the compiler on an application. |
| +class GlobalResult { |
| + /// Results grouped by package. |
| + final Map<String, BundleResult> packages = {}; |
| + |
| + /// Results for loose files, typically the entrypoint and files loaded via |
| + /// relative imports. |
| + final BundleResult loose = new BundleResult('*loose*'); |
| + |
| + /// Results from system libraries (dart:core, dart:async, etc). |
| + final BundleResult system = new BundleResult('*system*'); |
| + |
| + /// Add the result of a library in its corresponding group. |
| + void add(LibraryResult library) { |
| + if (library.uri.scheme == 'package') { |
| + var name = library.uri.pathSegments[0]; |
| + var package = packages.putIfAbsent(name, () => new BundleResult(name)); |
| + package.libraries.add(library); |
| + } else if (library.uri.scheme == 'dart') { |
| + system.libraries.add(library); |
| + } else { |
| + loose.libraries.add(library); |
| + } |
| + } |
| + |
| + // TODO(sigmund): consider splitting the serialization in multiple files so |
| + // that not all the information has to be stored in memory at once. |
| + List toJson() => [] |
| + ..addAll(packages.values.expand((p) => p.libraries.map((l) => l.toJson()))) |
| + ..addAll(loose.libraries.map((l) => l.toJson())) |
| + ..addAll(system.libraries.map((l) => l.toJson())); |
| + |
| + static GlobalResult fromJson(List json) { |
| + var res = new GlobalResult(); |
| + json.map(LibraryResult.fromJson).forEach(res.add); |
| + return res; |
| + } |
| + |
| + accept(ResultVisitor v) => v.visitGlobal(this); |
| +} |
| + |
| +/// Summarizes results for a group of libraries. Used by [GlobalResult] to group |
| +/// the systems libraries, loose libraries, and to create a separate a group per |
|
Johnni Winther
2015/07/13 19:21:44
'a separate a group' -> 'a separate group'
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
Ack. (this went away after refactoring)
|
| +/// package. |
| +class BundleResult { |
| + /// Name of the group. |
| + final String name; |
| + |
| + /// Library results that are part of this group. |
| + final List<LibraryResult> libraries = []; |
| + |
| + BundleResult(this.name); |
| +} |
| + |
| +/// Aggregate result for all units in a library. |
| +class LibraryResult { |
| + final Uri uri; |
| + final List<CompilationUnitResults> units = []; |
| + final List<String> classes = []; |
|
Johnni Winther
2015/07/13 19:21:44
What is this used for?
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
it was a placeholder for the future. now that I'm
|
| + LibraryResult(this.uri); |
| + accept(ResultVisitor v) => v.visitLibrary(this); |
| + |
| + Map toJson() => { |
| + 'uri': '$uri', |
| + 'units': units.map((p) => p.toJson()).toList(), |
| + }; |
| + |
| + static LibraryResult fromJson(Map json) => |
| + new LibraryResult(Uri.parse(json['uri'])) |
| + ..units.addAll(json['units'].map(CompilationUnitResult.fromJson)); |
| +} |
| + |
| +/// Results of a compilation unit (library or part). |
| +class CompilationUnitResult { |
| + /// Resolved Uri for this unit. |
| + final Uri uri; |
| + |
| + /// Results per function and method in this library |
| + final List<FunctionResult> functions = []; |
| + |
| + accept(ResultVisitor v) => v.visitUnit(this); |
| + |
| + CompilationUnitResult(this.uri); |
| + |
| + Map toJson() => { |
| + 'uri': '$uri', |
| + 'functions': functions.map((f) => f.toJson()).toList(), |
| + }; |
| + |
| + static CompilationUnitResult fromJson(Map json) => |
| + new CompilationUnitResult(Uri.parse(json['uri'])) |
| + ..functions.addAll(json['functions'].map(FunctionResult.fromJson)); |
| +} |
| + |
| +/// Results on a function. |
| +class FunctionResult { |
| + /// Name, if-any, of the function. |
| + final String name; |
| + |
| + /// Measurements collected. |
| + final Measurements measurements; |
| + |
| + FunctionResult(this.name, this.measurements); |
| + accept(ResultVisitor v) => v.visitFunction(this); |
| + |
| + Map toJson() => { |
| + 'name' : name, |
| + 'measurements': measurements.toJson(), |
| + }; |
| + |
| + static FunctionResult fromJson(Map json) => |
| + new FunctionResult(json['name'], |
| + Measurements.fromJson(json['measurements'])); |
| +} |
| + |
| +/// Top-level set of metrics |
| +const List<Metric> _topLevelMetrics = const [ |
| + Metric.functions, |
| + Metric.send, |
| +]; |
| + |
| +/// Apply `f` on each metric in DFS order on the metric "tree". |
| +visitAllMetrics(f) { |
| + var parentsStack = []; |
| + helper(Metric m) { |
| + f(m, parentsStack); |
| + if (m is GroupedMetric) { |
| + parentsStack.add(m); |
| + m.submetrics.forEach(helper); |
| + parentsStack.removeLast(); |
| + } |
| + } |
| + _topLevelMetrics.forEach(helper); |
| +} |
| + |
| +/// A metric we intend to measure. |
| +class Metric { |
| + /// Name for the metric. |
| + final String name; |
| + |
| + const Metric(this.name); |
| + |
| + String toString() => name; |
| + |
| + /// Total functions in a library/package/program. |
| + static const Metric functions = const GroupedMetric('functions', const [ |
| + reachableFunctions, |
| + ]); |
| + |
| + /// Subset of the functions that are reachable. |
| + static const Metric reachableFunctions = const Metric('reachable functions'); |
| + |
| + /// Parent of all send metrics. We classify sends as follows: |
| + /// |
| + /// sends |
| + /// |- monomorphic |
| + /// | |- static (top-levels, statics) |
| + /// | |- super |
| + /// | |- local (access to a local var, call local function) |
| + /// | |- constructor (like factory ctros) |
| + /// | |- type variable (reading a type variable) |
| + /// | |- nsm (known no such method exception) |
| + /// | |- single-nsm-call (known no such method call, single target) |
| + /// | |- instance (non-interceptor, only one possible target) |
| + /// | '- interceptor (interceptor, known) |
| + /// | |
| + /// '- polymorphic |
| + /// |- multi-nsm (known to be nSM, but not sure if error, or call, or |
| + /// which call) |
| + /// |- virtual (traditional virtual call, polymorphic equivalent of |
| + /// | `instance`, no-interceptor) |
| + /// |- multi-interceptor (1 of n possible interceptors) |
| + /// '- dynamic (any combination of the above) |
| + /// |
| + static const Metric send = const GroupedMetric('send', const [ |
| + monomorphicSend, |
| + polymorphicSend, |
| + ]); |
| + |
| + /// Parent of monomorphic sends, see [send] for details. |
| + static const Metric monomorphicSend = const GroupedMetric('monomorphic', |
| + const [ |
| + staticSend, |
| + superSend, |
| + localSend, |
| + constructorSend, |
| + typeVariableSend, |
| + nsmErrorSend, |
| + singleNsmCallSend, |
| + instanceSend, |
| + interceptorSend, |
| + ]); |
| + |
| + /// Metric for static calls, see [send] for details. |
| + static const Metric staticSend = const Metric('static'); |
| + |
| + /// Metric for super calls, see [send] for details. |
| + static const Metric superSend = const Metric('super'); |
| + |
| + /// Metric for local variable sends, see [send] for details. |
| + static const Metric localSend = const Metric('local'); |
| + |
| + /// Metric for constructor sends, see [send] for details. |
| + static const Metric constructorSend = const Metric('constructor'); |
| + |
| + /// Metric for type-variable sends, see [send] for details. |
| + // TODO(sigmund): delete? is mainly associated with compile-time errors |
| + static const Metric typeVariableSend = const Metric('type variable'); |
| + |
| + /// Metric for no-such-method errors, see [send] for details. |
| + static const Metric nsmErrorSend = const Metric('nSM error'); |
| + |
| + /// Metric for calls to noSuchMethod methods with a known target, see [send] |
| + /// for details. |
| + static const Metric singleNsmCallSend = const Metric('nSM call single'); |
| + |
| + /// Metric for calls to a precisely known instance method, see [send] for |
| + /// details. |
| + static const Metric instanceSend = const Metric('instance'); |
| + |
| + /// Metric for calls to a precisely known interceptor method, see [send] for |
| + /// details. |
| + static const Metric interceptorSend = const Metric('interceptor'); |
| + |
| + /// Parent of polymorphic sends, see [send] for details. |
| + static const Metric polymorphicSend = const GroupedMetric('polymorphic', |
| + const [ |
| + multiNsmCallSend, |
| + virtualSend, |
| + multiInterceptorSend, |
| + dynamicSend, |
| + ]); |
| + |
| + /// Metric for calls to noSuchMethod methods with more than one possible |
| + /// target, see [send] for details. |
| + static const Metric multiNsmCallSend = const Metric('nSM call multi'); |
| + |
| + /// Metric for calls that are dispatched virtually ar runtime, see [send] for |
| + /// details. |
| + static const Metric virtualSend = const Metric('virtual'); |
| + |
| + /// Metyric for calls to more than one possible interceptor, see [send] for |
| + /// details. |
| + static const Metric multiInterceptorSend = const Metric('interceptor multi'); |
| + |
| + /// Metyric for dynamic calls for which we know nothing about the target |
| + /// method. See [send] for details. |
| + static const Metric dynamicSend = const Metric('dynamic'); |
| + |
| + String toJson() => name; |
| + static Map<String, Metric> _nameToMetricMap = () { |
| + var res = {}; |
| + visitAllMetrics((m, _) => res[m.name] = m); |
| + return res; |
| + }(); |
| + |
| + static Metric fromJson(String name) => _nameToMetricMap[name]; |
| +} |
| + |
| +/// A metric that is subdivided in smaller metrics. |
| +class GroupedMetric extends Metric { |
| + final List<Metric> submetrics; |
| + |
| + const GroupedMetric(String name, this.submetrics) : super(name); |
| +} |
| + |
| +/// A measurement entry (practically a source-span location where the |
| +/// measurement was seen). |
| +class Entry { |
| + final int begin; |
| + final int end; |
| + Entry(this.begin, this.end); |
| +} |
| + |
| +/// A collection of data points for each metric. Used to summarize a single |
| +/// fucntion, a library, a package, or an entire program. |
|
Johnni Winther
2015/07/13 19:21:44
'fucntion' -> 'function'
Siggi Cherem (dart-lang)
2015/09/29 01:39:34
Done.
|
| +class Measurements { |
| + final Map<Metric, List<Entry>> entries; |
| + final Map<Metric, int> counters; |
| + |
| + Measurements() |
| + : entries = <Metric, List<Entry>>{}, |
| + counters = <Metric, int>{}; |
| + |
| + const Measurements.unreachableFunction() |
| + : counters = const { Metric.functions: 1}, entries = const {}; |
| + |
| + Measurements.reachableFunction() |
| + : counters = { Metric.functions: 1, Metric.reachableFunctions: 1}, |
| + entries = {}; |
| + |
| + /// Record [metric] was seen. The optional [begin] and [end] offsets are |
| + /// included for metrics that correspond to a source range. Intended to be |
| + /// used by `StatsBuilder`. |
| + record(Metric metric, [int begin, int end]) { |
| + if (begin != null && end != null) { |
| + entries.putIfAbsent(metric, () => []).add(new Entry(begin, end)); |
| + } |
| + counters.putIfAbsent(metric, () => 0); |
| + counters[metric]++; |
| + } |
| + |
| + /// Removes a previously added entry. Intended only to be used by |
| + /// `StatsBuilder`. Internally `StatsBuilder` computes redundant information |
| + /// in order to check for coverage and validate invariants with |
| + /// [checkInvariant]. This is used to adjust some of the redundant |
| + /// information. |
| + popLast(Metric metric) { |
| + assert(entries[metric] != null && entries[metric].isNotEmpty); |
| + entries[metric].removeLast(); |
| + counters[metric]--; |
| + } |
| + |
| + /// Add the counters from [other] into this set of measurements. |
| + addFrom(Measurements other) { |
| + other.counters.forEach((metric, value) { |
| + var current = counters[metric]; |
| + counters[metric] = current == null ? value : current + value; |
| + }); |
| + } |
| + |
| + /// Check that every grouped metric totals the individual counts of it's |
| + /// submetric. |
| + bool checkInvariant(GroupedMetric key) { |
| + // TODO(sigmund): use ?? operator. |
| + int total = counters[key]; |
| + if (total == null) total = 0; |
| + int submetricTotal = 0; |
| + for (var metric in key.submetrics) { |
| + var n = counters[metric]; |
| + if (n != null) submetricTotal += n; |
| + } |
| + return total == submetricTotal; |
| + } |
| + |
| + Map toJson() { |
| + var jsonEntries = <String, List<Map>>{}; |
| + entries.forEach((metric, values) { |
| + jsonEntries[metric.toJson()] = |
| + values.map((e) => {'begin': e.begin, 'end': e.end}).toList(); |
| + }); |
| + var json = {'entries': jsonEntries}; |
| + if (counters[Metric.functions] != null) { |
| + json[Metric.functions.toJson()] = counters[Metric.functions]; |
| + } |
| + if (counters[Metric.reachableFunctions] != null) { |
| + json[Metric.reachableFunctions.toJson()] = |
| + counters[Metric.reachableFunctions]; |
| + } |
| + return json; |
| + } |
| + |
| + static Measurements fromJson(Map json) { |
| + var res = new Measurements(); |
| + for (var key in json.keys) { |
| + var value = json[key]; |
| + if (value == null) continue; |
| + if (key == 'entries') { |
| + value.forEach((metric, jsonEntries) { |
| + jsonEntries.forEach((v) { |
| + res.record(Metric.fromJson(metric), v['begin'], v['end']); |
| + }); |
| + }); |
| + } else { |
| + res.counters[Metric.fromJson(key)] = value; |
| + } |
| + } |
| + return res; |
| + } |
| +} |
| + |
| +/// Simple visitor of the result hierarchy (useful for computing summaries for |
| +/// quick reports). |
| +abstract class ResultVisitor { |
| + visitGlobal(GlobalResult global); |
| + visitBundle(BundleResult group); |
| + visitLibrary(LibraryResult library); |
| + visitCompilationUnit(CompilationUnitResult unit); |
| + visitFunction(FunctionResult functino); |
| +} |
| + |
| +/// Recursive visitor that visits every function starting from the global |
| +/// results. |
| +abstract class RecursiveResultVisitor extends ResultVisitor { |
| + visitGlobal(GlobalResult global) { |
| + global.packages.values.forEach(visitBundle); |
| + visitBundle(global.system); |
| + visitBundle(global.loose); |
| + } |
| + |
| + visitBundle(BundleResult group) { |
| + group.libraries.forEach(visitLibrary); |
| + } |
| + |
| + visitLibrary(LibraryResult library) { |
| + library.units.forEach(visitCompilationUnit); |
| + } |
| + |
| + visitCompilationUnit(CompilationUnitResult unit) { |
| + unit.functions.forEach(visitFunction); |
| + } |
| +} |
| + |
| +/// Color-highighted string used mainly to debug invariants. |
|
Johnni Winther
2015/07/13 19:21:44
'highighted' -> 'highlighted'
|
| +String recursiveDiagnosticString(Measurements measurements, Metric metric) { |
| + var sb = new StringBuffer(); |
| + helper(Metric m) { |
| + int value = measurements.counters[m]; |
| + if (value == null) value = 0; |
| + if (m is! GroupedMetric) { |
| + sb.write(value); |
| + sb.write(' ${m.name}'); |
| + return; |
| + } |
| + GroupedMetric group = m; |
| + |
| + int expected = 0; |
| + for (var sub in group.submetrics) { |
| + var n = measurements.counters[sub]; |
| + if (n != null) expected += n; |
| + } |
| + if (value == expected) { |
| + sb.write('[32m'); |
|
Johnni Winther
2015/07/13 19:21:44
Note that colors are not supported on Windows by d
|
| + sb.write(value); |
| + } else { |
| + sb.write('[31m'); |
| + sb.write(value); |
| + sb.write('[33m['); |
| + sb.write(expected); |
| + sb.write(']'); |
| + } |
| + sb.write('[0m'); |
| + sb.write(' ${group.name}'); |
| + |
| + bool first = true; |
| + sb.write('('); |
| + for (var sub in group.submetrics) { |
| + if (first) { |
| + first = false; |
| + } else { |
| + sb.write(' + '); |
| + } |
| + helper(sub); |
| + } |
| + sb.write(')'); |
| + } |
| + helper(metric); |
| + return sb.toString(); |
| +} |