blob: ede436380347fa13b52b23cd0ae26c7c8c3e8209 [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.
/// This file supports profiling dynamic calls.
part of dart._debugger;
class _MethodStats {
final String typeName;
final String frame;
double count = 0.0;
_MethodStats(this.typeName, this.frame);
}
class _CallMethodRecord {
var jsError;
var type;
_CallMethodRecord(this.jsError, this.type);
}
/// Size for the random sample of dynamic calls.
int _callRecordSampleSize = 5000;
/// If the number of dynamic calls exceeds [_callRecordSampleSize] this list
/// will represent a random sample of the dynamic calls made.
var _callMethodRecords = <_CallMethodRecord>[];
/// If the number of dynamic calls exceeds [_callRecordSampleSize] this value
/// will be greater than [_callMethodRecords.length].
int _totalCallRecords = 0;
/// Minimum number of samples to consider a profile entry relevant.
/// This could be set a lot higher. We set this value so users are not
/// confused into thinking that a dynamic call that occurred once but was
/// randomly included in the sample is relevant.
num _minCount = 2;
/// Cache mapping from raw stack frames to source mapped stack frames to
/// speedup lookup of source map frames when running the profiler.
/// The number of source map entries looked up makes caching more important
/// in this case than for typical source map use cases.
var _frameMappingCache = <String, String>{};
List<List<Object>> getDynamicStats() {
// Process the accumulated method stats. This may be quite slow as processing
// stack traces is expensive. If there are performance blockers, we should
// switch to a sampling approach that caps the number of _callMethodRecords
// and uses random sampling to decide whether to add each additional record
// to the sample. Main change required is that we need to still show the total
// raw number of dynamic calls so that the magnitude of the dynamic call
// performance hit is clear to users.
var callMethodStats = <String, _MethodStats>{};
if (_callMethodRecords.length > 0) {
// Ratio between total record count and sampled records count.
var recordRatio = _totalCallRecords / _callMethodRecords.length;
for (var record in _callMethodRecords) {
String stackStr = JS<String>('!', '#.stack', record.jsError);
var frames = stackStr.split('\n');
// Skip first two lines as the first couple frames are from the dart
// runtime.
var src = frames
.skip(2)
.map((f) => _frameMappingCache.putIfAbsent(
f, () => stackTraceMapper!('\n$f')))
.firstWhere((f) => !f.startsWith('dart:'), orElse: () => '');
var actualTypeName = dart.typeName(record.type);
callMethodStats
.putIfAbsent(
"$actualTypeName <$src>", () => _MethodStats(actualTypeName, src))
.count += recordRatio;
}
// filter out all calls that did not occur at least _minCount times in the
// random sample if we are dealing with a random sample instead of a
// complete profile.
if (_totalCallRecords != _callMethodRecords.length) {
for (var k in callMethodStats.keys.toList()) {
var stats = callMethodStats[k]!;
var threshold = _minCount * recordRatio;
if (stats.count + 0.001 < threshold) {
callMethodStats.remove(k);
}
}
}
}
_callMethodRecords.clear();
_totalCallRecords = 0;
var keys = callMethodStats.keys.toList();
keys.sort(
(a, b) => callMethodStats[b]!.count.compareTo(callMethodStats[a]!.count));
var ret = <List<Object>>[];
for (var key in keys) {
var stats = callMethodStats[key]!;
ret.add([stats.typeName, stats.frame, stats.count.round()]);
}
return ret;
}
clearDynamicStats() {
_callMethodRecords.clear();
}
// We need to set this property while the sdk is only partially initialized
// so we cannot use a regular Dart field.
bool get _trackProfile => JS<bool>('!', 'dart.__trackProfile');
trackCall(obj) {
if (JS<bool>('!', '!#', _trackProfile)) return;
int index = -1;
_totalCallRecords++;
if (_callMethodRecords.length == _callRecordSampleSize) {
// Ensure that each sample has an equal
// _callRecordSampleSize / _totalCallRecords chance of inclusion
// by choosing to include the new record in the sample the with the
// appropriate probability randomly evicting one of the existing records.
// Unfortunately we can't use the excellent Random.nextInt method defined
// by Dart from within this library.
index = JS<int>('!', 'Math.floor(Math.random() * #)', _totalCallRecords);
if (index >= _callMethodRecords.length) return; // don't sample
}
var record =
_CallMethodRecord(JS('', 'new Error()'), dart.getReifiedType(obj));
if (index == -1) {
_callMethodRecords.add(record);
} else {
_callMethodRecords[index] = record;
}
}