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(); |
+} |