OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// This file supports profiling dynamic calls. |
| 6 part of dart._runtime; |
| 7 |
| 8 class _MethodStats { |
| 9 final String typeName; |
| 10 final String frame; |
| 11 double count; |
| 12 |
| 13 _MethodStats(this.typeName, this.frame) { |
| 14 count = 0.0; |
| 15 } |
| 16 } |
| 17 |
| 18 class _CallMethodRecord { |
| 19 var jsError; |
| 20 var type; |
| 21 |
| 22 _CallMethodRecord(this.jsError, this.type); |
| 23 } |
| 24 |
| 25 /// Size for the random sample of dynamic calls. |
| 26 int _callRecordSampleSize = 5000; |
| 27 |
| 28 /// If the number of dynamic calls exceeds [_callRecordSampleSize] this list |
| 29 /// will represent a random sample of the dynamic calls made. |
| 30 List<_CallMethodRecord> _callMethodRecords = new List(); |
| 31 |
| 32 /// If the number of dynamic calls exceeds [_callRecordSampleSize] this value |
| 33 /// will be greater than [_callMethodRecords.length]. |
| 34 int _totalCallRecords = 0; |
| 35 |
| 36 /// Minimum number of samples to consider a profile entry relevant. |
| 37 /// This could be set a lot higher. We set this value so users are not |
| 38 /// confused into thinking that a dynamic call that occurred once but was |
| 39 /// randomly included in the sample is relevant. |
| 40 num _minCount = 2; |
| 41 |
| 42 /// Cache mapping from raw stack frames to source mapped stack frames to |
| 43 /// speedup lookup of source map frames when running the profiler. |
| 44 /// The number of source map entries looked up makes caching more important |
| 45 /// in this case than for typical source map use cases. |
| 46 Map<String, String> _frameMappingCache = new Map(); |
| 47 |
| 48 List<List<Object>> getDynamicStats() { |
| 49 // Process the accumulated method stats. This may be quite slow as processing |
| 50 // stack traces is expensive. If there are performance blockers, we should |
| 51 // switch to a sampling approach that caps the number of _callMethodRecords |
| 52 // and uses random sampling to decide whether to add each additional record |
| 53 // to the sample. Main change required is that we need to still show the total |
| 54 // raw number of dynamic calls so that the magnitude of the dynamic call |
| 55 // performance hit is clear to users. |
| 56 |
| 57 Map<String, _MethodStats> callMethodStats = new Map(); |
| 58 if (_callMethodRecords.length > 0) { |
| 59 // Ratio between total record count and sampled records count. |
| 60 var recordRatio = _totalCallRecords / _callMethodRecords.length; |
| 61 for (var record in _callMethodRecords) { |
| 62 var stackStr = JS('String', '#.stack', record.jsError); |
| 63 var frames = stackStr.split('\n'); |
| 64 var src = ''; |
| 65 // Skip first two lines as the first couple frames are from the dart |
| 66 // runtime. |
| 67 for (int i = 2; i < frames.length; ++i) { |
| 68 var frame = frames[i]; |
| 69 var mappedFrame = _frameMappingCache.putIfAbsent(frame, () { |
| 70 return stackTraceMapper('\n${frame}'); |
| 71 }); |
| 72 if (!mappedFrame.contains('dart:_runtime/operations.dart') && |
| 73 !mappedFrame.contains('dart:_runtime/profile.dart')) { |
| 74 src = mappedFrame; |
| 75 break; |
| 76 } |
| 77 } |
| 78 |
| 79 var actualTypeName = typeName(record.type); |
| 80 callMethodStats |
| 81 .putIfAbsent("$actualTypeName <$src>", |
| 82 () => new _MethodStats(actualTypeName, src)) |
| 83 .count += recordRatio; |
| 84 } |
| 85 |
| 86 // filter out all calls that did not occur at least _minCount times in the |
| 87 // random sample if we are dealing with a random sample instead of a |
| 88 // complete profile. |
| 89 if (_totalCallRecords != _callMethodRecords.length) { |
| 90 for (var k in callMethodStats.keys.toList()) { |
| 91 var stats = callMethodStats[k]; |
| 92 var threshold = _minCount * recordRatio; |
| 93 if (stats.count + 0.001 < threshold) { |
| 94 callMethodStats.remove(k); |
| 95 } |
| 96 } |
| 97 } |
| 98 } |
| 99 _callMethodRecords.clear(); |
| 100 _totalCallRecords = 0; |
| 101 var keys = callMethodStats.keys.toList(); |
| 102 |
| 103 keys.sort( |
| 104 (a, b) => callMethodStats[b].count.compareTo(callMethodStats[a].count)); |
| 105 List<List<Object>> ret = []; |
| 106 for (var key in keys) { |
| 107 var stats = callMethodStats[key]; |
| 108 ret.add([stats.typeName, stats.frame, stats.count.round()]); |
| 109 } |
| 110 return ret; |
| 111 } |
| 112 |
| 113 clearDynamicStats() { |
| 114 _callMethodRecords.clear(); |
| 115 } |
| 116 |
| 117 bool trackProfile = JS('bool', 'dart.global.trackDdcProfile'); |
| 118 |
| 119 _trackCall(obj) { |
| 120 if (JS('bool', '!#', trackProfile)) return; |
| 121 int index = -1; |
| 122 _totalCallRecords++; |
| 123 if (_callMethodRecords.length == _callRecordSampleSize) { |
| 124 // Ensure that each sample has an equal |
| 125 // _callRecordSampleSize / _totalCallRecords chance of inclusion |
| 126 // by choosing to include the new record in the sample the with the |
| 127 // appropriate probability randomly evicting one of the existing records. |
| 128 // Unfortunately we can't use the excellent Random.nextInt method defined |
| 129 // by Dart from within this library. |
| 130 index = JS('int', 'Math.floor(Math.random() * #)', _totalCallRecords); |
| 131 if (index >= _callMethodRecords.length) return; // don't sample |
| 132 } |
| 133 var record = |
| 134 new _CallMethodRecord(JS('', 'new Error()'), getReifiedType(obj)); |
| 135 if (index == -1) { |
| 136 _callMethodRecords.add(record); |
| 137 } else { |
| 138 _callMethodRecords[index] = record; |
| 139 } |
| 140 } |
OLD | NEW |