blob: 1d13c5b45973c92df757bbc064fd5489aa0aa66a [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.
/// Measurements collected about individual functions. Currently we compute
/// data about "sends", to classify whether we know the target or not.
library dart2js_info.src.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. [Metric.functions]
/// and [Metric.send] are the top level metrics. See those declarations for
/// details on the subtrees.
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);
factory Metric.fromName(String name) => _nameToMetricMap[name];
String toString() => name;
/// Total functions in a library/package/program. Parent of
/// [reachableFunction].
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');
static Map<String, Metric> _nameToMetricMap = () {
var res = <String, Metric>{};
visitAllMetrics((m, _) => res[m.name] = m);
return res;
}();
}
/// 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
/// function, a library, a package, or an entire program.
class Measurements {
final Uri uri;
final Map<Metric, List<Entry>> entries;
final Map<Metric, int> counters;
Measurements([this.uri])
: entries = <Metric, List<Entry>>{},
counters = <Metric, int>{};
const Measurements.unreachableFunction()
: counters = const {Metric.functions: 1},
entries = const {},
uri = null;
Measurements.reachableFunction([this.uri])
: 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) {
assert(uri != 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) {
int total = counters[key] ?? 0;
int submetricTotal = 0;
for (var metric in key.submetrics) {
var n = counters[metric];
if (n != null) submetricTotal += n;
}
return total == submetricTotal;
}
}