Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// This library defines runtime operations on objects used by the code | 5 /// This library defines runtime operations on objects used by the code |
| 6 /// generator. | 6 /// generator. |
| 7 part of dart._runtime; | 7 part of dart._runtime; |
| 8 | 8 |
| 9 class InvocationImpl extends Invocation { | 9 class InvocationImpl extends Invocation { |
| 10 final Symbol memberName; | 10 final Symbol memberName; |
| (...skipping 346 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 357 (resolvedField) => _callMethod(obj, resolvedField, typeArgs, args, method)); | 357 (resolvedField) => _callMethod(obj, resolvedField, typeArgs, args, method)); |
| 358 | 358 |
| 359 dsendRepl(obj, method, @rest args) => _callMethodRepl(obj, method, null, args); | 359 dsendRepl(obj, method, @rest args) => _callMethodRepl(obj, method, null, args); |
| 360 | 360 |
| 361 dgsendRepl(obj, typeArgs, method, @rest args) => | 361 dgsendRepl(obj, typeArgs, method, @rest args) => |
| 362 _callMethodRepl(obj, method, typeArgs, args); | 362 _callMethodRepl(obj, method, typeArgs, args); |
| 363 | 363 |
| 364 class _MethodStats { | 364 class _MethodStats { |
| 365 final String typeName; | 365 final String typeName; |
| 366 final String frame; | 366 final String frame; |
| 367 int count; | 367 double count; |
|
Jacob
2017/04/12 18:24:42
Switch to a a random sampling profiler to make pro
| |
| 368 | 368 |
| 369 _MethodStats(this.typeName, this.frame) { | 369 _MethodStats(this.typeName, this.frame) { |
| 370 count = 0; | 370 count = 0.0; |
| 371 } | 371 } |
| 372 } | 372 } |
| 373 | 373 |
| 374 class _CallMethodRecord { | |
| 375 var jsError; | |
| 376 var type; | |
| 377 | |
| 378 _CallMethodRecord(this.jsError, this.type); | |
| 379 } | |
| 380 | |
| 374 Map<String, _MethodStats> _callMethodStats = new Map(); | 381 Map<String, _MethodStats> _callMethodStats = new Map(); |
| 375 | 382 |
| 383 /// Size for the random sample of dynamic calls. | |
| 384 int _callRecordSampleSize = 5000; | |
| 385 | |
| 386 /// If the number of dynamic calls exceeds [_callRecordSampleSize] this list | |
| 387 /// will represent a random sample of the dynamic calls made. | |
| 388 List<_CallMethodRecord> _callMethodRecords = new List(); | |
| 389 | |
| 390 /// If the number of dynamic calls exceeds [_callRecordSampleSize] this value | |
| 391 /// will be greater than [_callMethodRecords.length]. | |
| 392 int _totalCallRecords = 0; | |
| 393 | |
| 394 /// Minimum number of samples to consider a profile entry relevant. | |
| 395 /// This could be set a lot higher. We set this value so users are not | |
| 396 /// confused into thinking that a dynamic call that occurred once but was | |
| 397 /// randomly included in the sample is relevant. | |
| 398 num _minCount = 2; | |
| 399 | |
| 400 /// Cache mapping from raw stack frames to source mapped stack frames to | |
| 401 /// speedup lookup of source map frames when running the profiler. | |
| 402 /// The number of source map entries looked up makes caching more important | |
| 403 /// in this case than for typical source map use cases. | |
| 404 Map<String, String> _frameMappingCache = new Map(); | |
| 405 | |
| 376 List<List<Object>> getDynamicStats() { | 406 List<List<Object>> getDynamicStats() { |
| 377 List<List<Object>> ret = []; | 407 List<List<Object>> ret = []; |
| 408 // Process the accumulated method stats. This may be quite slow as processing | |
| 409 // stack traces is expensive. If there are performance blockers, we should | |
| 410 // switch to a sampling approach that caps the number of _callMethodRecords | |
| 411 // and uses random sampling to decide whether to add each additional record | |
| 412 // to the sample. Main change required is that we need to still show the total | |
| 413 // raw number of dynamic calls so that the magnitude of the dynamic call | |
| 414 // performance hit is clear to users. | |
| 378 | 415 |
| 416 if (_callMethodRecords.length > 0) { | |
| 417 // Ratio between total record count and sampled records count. | |
| 418 var recordRatio = _totalCallRecords / _callMethodRecords.length; | |
| 419 for (var record in _callMethodRecords) { | |
| 420 var stackStr = JS('String', '#.stack', record.jsError); | |
| 421 // Truncate the stacktrace as we are only interested in the top 3-4 frames . | |
| 422 var frames = stackStr.split('\n'); | |
| 423 var src = ''; | |
| 424 for (int i = 2; i < frames.length; ++i) { | |
| 425 var frame = frames[i]; | |
| 426 var mappedFrame = _frameMappingCache.putIfAbsent(frame, () { | |
| 427 return stackTraceMapper('\n${frame}'); | |
| 428 }); | |
| 429 if (!mappedFrame.contains( | |
| 430 'dart:_internal/js_runtime/lib/ddc_runtime/operations.dart')) { | |
| 431 src = mappedFrame; | |
| 432 break; | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 var actualTypeName = typeName(record.type); | |
| 437 _callMethodStats | |
| 438 .putIfAbsent("$actualTypeName <$src>", | |
| 439 () => new _MethodStats(actualTypeName, src)) | |
| 440 .count += recordRatio; | |
| 441 } | |
| 442 | |
| 443 for (var k in _callMethodStats.keys.toList()) { | |
| 444 var stats = _callMethodStats[k]; | |
| 445 // filter out all calls that did not occur at least _minCount times in the | |
| 446 // random sample. | |
| 447 var threshold = _minCount * recordRatio; | |
| 448 if (stats.count + 0.001 < threshold) { | |
| 449 _callMethodStats.remove(k); | |
| 450 } | |
| 451 } | |
| 452 } | |
| 453 _callMethodRecords.clear(); | |
| 454 _totalCallRecords = 0; | |
| 379 var keys = _callMethodStats.keys.toList(); | 455 var keys = _callMethodStats.keys.toList(); |
| 380 | 456 |
| 381 keys.sort( | 457 keys.sort( |
| 382 (a, b) => _callMethodStats[b].count.compareTo(_callMethodStats[a].count)); | 458 (a, b) => _callMethodStats[b].count.compareTo(_callMethodStats[a].count)); |
| 383 for (var key in keys) { | 459 for (var key in keys) { |
| 384 var stats = _callMethodStats[key]; | 460 var stats = _callMethodStats[key]; |
| 385 ret.add([stats.typeName, stats.frame, stats.count]); | 461 ret.add([stats.typeName, stats.frame, stats.count.round()]); |
| 386 } | 462 } |
| 387 | 463 |
| 388 return ret; | 464 return ret; |
| 389 } | 465 } |
| 390 | 466 |
| 391 clearDynamicStats() { | 467 clearDynamicStats() { |
| 392 _callMethodStats.clear(); | 468 _callMethodStats.clear(); |
| 469 _callMethodRecords.clear(); | |
| 393 } | 470 } |
| 394 | 471 |
| 395 bool trackProfile = JS('bool', 'dart.global.trackDdcProfile'); | 472 bool trackProfile = JS('bool', 'dart.global.trackDdcProfile'); |
| 396 | 473 |
| 397 _trackCall(obj) { | 474 _trackCall(obj) { |
| 398 if (JS('bool', '!#', trackProfile)) return; | 475 if (JS('bool', '!#', trackProfile)) return; |
| 399 | 476 int index = -1; |
| 400 var actual = getReifiedType(obj); | 477 _totalCallRecords++; |
| 401 String stackStr = JS('String', "new Error().stack"); | 478 if (_callMethodRecords.length == _callRecordSampleSize) { |
| 402 var stack = stackStr.split('\n at '); | 479 // Unfortunately we can't use the excellent Random.nextInt method defined |
| 403 var src = ''; | 480 // by Dart from within this library. |
| 404 for (int i = 2; i < stack.length; ++i) { | 481 index = JS('int', 'Math.floor(Math.random() * #)', _totalCallRecords); |
| 405 var frame = stack[i]; | 482 if (index >= _callMethodRecords.length) return; // don't sample |
| 406 if (!frame.contains('dart_sdk.js')) { | |
| 407 src = frame; | |
| 408 break; | |
| 409 } | |
| 410 } | 483 } |
| 411 | 484 var record = |
| 412 var actualTypeName = typeName(actual); | 485 new _CallMethodRecord(JS('', 'new Error()'), getReifiedType(obj)); |
| 413 _callMethodStats | 486 if (index == -1) { |
| 414 .putIfAbsent( | 487 _callMethodRecords.add(record); |
| 415 "$actualTypeName <$src>", () => new _MethodStats(actualTypeName, src)) | 488 } else { |
| 416 .count++; | 489 _callMethodRecords[index] = record; |
| 490 } | |
| 417 } | 491 } |
| 418 | 492 |
| 419 /// Shared code for dsend, dindex, and dsetindex. | 493 /// Shared code for dsend, dindex, and dsetindex. |
| 420 _callMethod(obj, name, typeArgs, args, displayName) { | 494 _callMethod(obj, name, typeArgs, args, displayName) { |
| 421 var symbol = _canonicalMember(obj, name); | 495 var symbol = _canonicalMember(obj, name); |
| 422 if (symbol == null) { | 496 if (symbol == null) { |
| 423 return noSuchMethod( | 497 return noSuchMethod( |
| 424 obj, new InvocationImpl(displayName, args, isMethod: true)); | 498 obj, new InvocationImpl(displayName, args, isMethod: true)); |
| 425 } | 499 } |
| 426 var f = obj != null ? JS('', '#[#]', obj, symbol) : null; | 500 var f = obj != null ? JS('', '#[#]', obj, symbol) : null; |
| (...skipping 556 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 983 name = '+' + name; | 1057 name = '+' + name; |
| 984 } | 1058 } |
| 985 return name; | 1059 return name; |
| 986 } | 1060 } |
| 987 | 1061 |
| 988 /// Emulates the implicit "loadLibrary" function provided by a deferred library. | 1062 /// Emulates the implicit "loadLibrary" function provided by a deferred library. |
| 989 /// | 1063 /// |
| 990 /// Libraries are not actually deferred in DDC, so this just returns a future | 1064 /// Libraries are not actually deferred in DDC, so this just returns a future |
| 991 /// that completes immediately. | 1065 /// that completes immediately. |
| 992 Future loadLibrary() => new Future.value(); | 1066 Future loadLibrary() => new Future.value(); |
| OLD | NEW |