| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of cpu_profiler; | |
| 6 | |
| 7 abstract class CallTreeNode<NodeT extends M.CallTreeNode> | |
| 8 implements M.CallTreeNode { | |
| 9 final List<NodeT> children; | |
| 10 final int count; | |
| 11 final int inclusiveNativeAllocations; | |
| 12 final int exclusiveNativeAllocations; | |
| 13 double get percentage => _percentage; | |
| 14 double _percentage = 0.0; | |
| 15 final Set<String> attributes = new Set<String>(); | |
| 16 | |
| 17 // Either a ProfileCode or a ProfileFunction. | |
| 18 Object get profileData; | |
| 19 String get name; | |
| 20 | |
| 21 CallTreeNode(this.children, this.count, this.inclusiveNativeAllocations, | |
| 22 this.exclusiveNativeAllocations); | |
| 23 } | |
| 24 | |
| 25 class CodeCallTreeNode extends CallTreeNode<CodeCallTreeNode> | |
| 26 implements M.CodeCallTreeNode { | |
| 27 final ProfileCode profileCode; | |
| 28 | |
| 29 Object get profileData => profileCode; | |
| 30 | |
| 31 String get name => profileCode.code.name; | |
| 32 | |
| 33 final Set<String> attributes = new Set<String>(); | |
| 34 CodeCallTreeNode(this.profileCode, int count, int inclusiveNativeAllocations, | |
| 35 int exclusiveNativeAllocations) | |
| 36 : super(new List<CodeCallTreeNode>(), count, inclusiveNativeAllocations, | |
| 37 exclusiveNativeAllocations) { | |
| 38 attributes.addAll(profileCode.attributes); | |
| 39 } | |
| 40 } | |
| 41 | |
| 42 class CallTree<NodeT extends CallTreeNode> { | |
| 43 final bool inclusive; | |
| 44 final NodeT root; | |
| 45 | |
| 46 CallTree(this.inclusive, this.root); | |
| 47 } | |
| 48 | |
| 49 class CodeCallTree extends CallTree<CodeCallTreeNode> | |
| 50 implements M.CodeCallTree { | |
| 51 CodeCallTree(bool inclusive, CodeCallTreeNode root) : super(inclusive, root) { | |
| 52 if ((root.inclusiveNativeAllocations != null) && | |
| 53 (root.inclusiveNativeAllocations != 0)) { | |
| 54 _setCodeMemoryPercentage(null, root); | |
| 55 } else { | |
| 56 _setCodePercentage(null, root); | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 CodeCallTree filtered(CallTreeNodeFilter filter) { | |
| 61 var treeFilter = new _FilteredCodeCallTreeBuilder(filter, this); | |
| 62 treeFilter.build(); | |
| 63 if ((treeFilter.filtered.root.inclusiveNativeAllocations != null) && | |
| 64 (treeFilter.filtered.root.inclusiveNativeAllocations != 0)) { | |
| 65 _setCodeMemoryPercentage(null, treeFilter.filtered.root); | |
| 66 } else { | |
| 67 _setCodePercentage(null, treeFilter.filtered.root); | |
| 68 } | |
| 69 return treeFilter.filtered; | |
| 70 } | |
| 71 | |
| 72 _setCodePercentage(CodeCallTreeNode parent, CodeCallTreeNode node) { | |
| 73 assert(node != null); | |
| 74 var parentPercentage = 1.0; | |
| 75 var parentCount = node.count; | |
| 76 if (parent != null) { | |
| 77 parentPercentage = parent._percentage; | |
| 78 parentCount = parent.count; | |
| 79 } | |
| 80 if (inclusive) { | |
| 81 node._percentage = parentPercentage * (node.count / parentCount); | |
| 82 } else { | |
| 83 node._percentage = (node.count / parentCount); | |
| 84 } | |
| 85 for (var child in node.children) { | |
| 86 _setCodePercentage(node, child); | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 _setCodeMemoryPercentage(CodeCallTreeNode parent, CodeCallTreeNode node) { | |
| 91 assert(node != null); | |
| 92 var parentPercentage = 1.0; | |
| 93 var parentMemory = node.inclusiveNativeAllocations; | |
| 94 if (parent != null) { | |
| 95 parentPercentage = parent._percentage; | |
| 96 parentMemory = parent.inclusiveNativeAllocations; | |
| 97 } | |
| 98 if (inclusive) { | |
| 99 node._percentage = | |
| 100 parentPercentage * (node.inclusiveNativeAllocations / parentMemory); | |
| 101 } else { | |
| 102 node._percentage = (node.inclusiveNativeAllocations / parentMemory); | |
| 103 } | |
| 104 for (var child in node.children) { | |
| 105 _setCodeMemoryPercentage(node, child); | |
| 106 } | |
| 107 node.children.sort((a, b) { | |
| 108 return b.inclusiveNativeAllocations - a.inclusiveNativeAllocations; | |
| 109 }); | |
| 110 } | |
| 111 | |
| 112 _recordCallerAndCalleesInner( | |
| 113 CodeCallTreeNode caller, CodeCallTreeNode callee) { | |
| 114 if (caller != null) { | |
| 115 caller.profileCode._recordCallee(callee.profileCode, callee.count); | |
| 116 callee.profileCode._recordCaller(caller.profileCode, caller.count); | |
| 117 } | |
| 118 | |
| 119 for (var child in callee.children) { | |
| 120 _recordCallerAndCalleesInner(callee, child); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 _recordCallerAndCallees() { | |
| 125 for (var child in root.children) { | |
| 126 _recordCallerAndCalleesInner(null, child); | |
| 127 } | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 class FunctionCallTreeNodeCode { | |
| 132 final ProfileCode code; | |
| 133 final int ticks; | |
| 134 FunctionCallTreeNodeCode(this.code, this.ticks); | |
| 135 } | |
| 136 | |
| 137 class FunctionCallTreeNode extends CallTreeNode { | |
| 138 final ProfileFunction profileFunction; | |
| 139 final codes = new List<FunctionCallTreeNodeCode>(); | |
| 140 int _totalCodeTicks = 0; | |
| 141 int get totalCodesTicks => _totalCodeTicks; | |
| 142 | |
| 143 String get name => M.getFunctionFullName(profileFunction.function); | |
| 144 Object get profileData => profileFunction; | |
| 145 | |
| 146 FunctionCallTreeNode(this.profileFunction, int count, | |
| 147 inclusiveNativeAllocations, exclusiveNativeAllocations) | |
| 148 : super(new List<FunctionCallTreeNode>(), count, | |
| 149 inclusiveNativeAllocations, exclusiveNativeAllocations) { | |
| 150 profileFunction._addKindBasedAttributes(attributes); | |
| 151 } | |
| 152 | |
| 153 // Does this function have an optimized version of itself? | |
| 154 bool hasOptimizedCode() { | |
| 155 for (var nodeCode in codes) { | |
| 156 var profileCode = nodeCode.code; | |
| 157 if (!profileCode.code.isDartCode) { | |
| 158 continue; | |
| 159 } | |
| 160 if (profileCode.code.function != profileFunction.function) { | |
| 161 continue; | |
| 162 } | |
| 163 if (profileCode.code.isOptimized) { | |
| 164 return true; | |
| 165 } | |
| 166 } | |
| 167 return false; | |
| 168 } | |
| 169 | |
| 170 // Does this function have an unoptimized version of itself? | |
| 171 bool hasUnoptimizedCode() { | |
| 172 for (var nodeCode in codes) { | |
| 173 var profileCode = nodeCode.code; | |
| 174 if (!profileCode.code.isDartCode) { | |
| 175 continue; | |
| 176 } | |
| 177 if (profileCode.code.kind == M.CodeKind.stub) { | |
| 178 continue; | |
| 179 } | |
| 180 if (!profileCode.code.isOptimized) { | |
| 181 return true; | |
| 182 } | |
| 183 } | |
| 184 return false; | |
| 185 } | |
| 186 | |
| 187 // Has this function been inlined in another function? | |
| 188 bool isInlined() { | |
| 189 for (var nodeCode in codes) { | |
| 190 var profileCode = nodeCode.code; | |
| 191 if (!profileCode.code.isDartCode) { | |
| 192 continue; | |
| 193 } | |
| 194 if (profileCode.code.kind == M.CodeKind.stub) { | |
| 195 continue; | |
| 196 } | |
| 197 // If the code's function isn't this function. | |
| 198 if (profileCode.code.function != profileFunction.function) { | |
| 199 return true; | |
| 200 } | |
| 201 } | |
| 202 return false; | |
| 203 } | |
| 204 | |
| 205 setCodeAttributes() {} | |
| 206 } | |
| 207 | |
| 208 /// Predicate filter function. Returns true if path from root to [node] and all | |
| 209 /// of [node]'s children should be added to the filtered tree. | |
| 210 typedef bool CallTreeNodeFilter(CallTreeNode node); | |
| 211 | |
| 212 /// Build a filter version of a FunctionCallTree. | |
| 213 abstract class _FilteredCallTreeBuilder { | |
| 214 /// The filter. | |
| 215 final CallTreeNodeFilter filter; | |
| 216 | |
| 217 /// The unfiltered tree. | |
| 218 final CallTree _unfilteredTree; | |
| 219 | |
| 220 /// The filtered tree (construct by [build]). | |
| 221 final CallTree filtered; | |
| 222 final List _currentPath = []; | |
| 223 | |
| 224 /// Construct a filtered tree builder using [filter] and [tree]. | |
| 225 _FilteredCallTreeBuilder(this.filter, CallTree tree, this.filtered) | |
| 226 : _unfilteredTree = tree; | |
| 227 | |
| 228 /// Build the filtered tree. | |
| 229 build() { | |
| 230 assert(filtered != null); | |
| 231 assert(filter != null); | |
| 232 assert(_unfilteredTree != null); | |
| 233 _descend(_unfilteredTree.root); | |
| 234 } | |
| 235 | |
| 236 CallTreeNode _findInChildren(CallTreeNode current, CallTreeNode needle) { | |
| 237 for (var child in current.children) { | |
| 238 if (child.profileData == needle.profileData) { | |
| 239 return child; | |
| 240 } | |
| 241 } | |
| 242 return null; | |
| 243 } | |
| 244 | |
| 245 CallTreeNode _copyNode(CallTreeNode node); | |
| 246 | |
| 247 /// Add all nodes in [_currentPath]. | |
| 248 FunctionCallTreeNode _addCurrentPath() { | |
| 249 FunctionCallTreeNode current = filtered.root; | |
| 250 // Tree root is always the first element of the current path. | |
| 251 assert(_unfilteredTree.root == _currentPath[0]); | |
| 252 // Assert that unfiltered tree's root and filtered tree's root are different
. | |
| 253 assert(_unfilteredTree.root != current); | |
| 254 for (var i = 1; i < _currentPath.length; i++) { | |
| 255 // toAdd is from the unfiltered tree. | |
| 256 var toAdd = _currentPath[i]; | |
| 257 // See if we already have a node for toAdd in the filtered tree. | |
| 258 var child = _findInChildren(current, toAdd); | |
| 259 if (child == null) { | |
| 260 // New node. | |
| 261 child = _copyNode(toAdd); | |
| 262 current.children.add(child); | |
| 263 } | |
| 264 current = child; | |
| 265 assert(current.count == toAdd.count); | |
| 266 } | |
| 267 return current; | |
| 268 } | |
| 269 | |
| 270 /// Starting at [current] append [next] and all of [next]'s sub-trees | |
| 271 _appendTree(CallTreeNode current, CallTreeNode next) { | |
| 272 if (next == null) { | |
| 273 return; | |
| 274 } | |
| 275 var child = _findInChildren(current, next); | |
| 276 if (child == null) { | |
| 277 child = _copyNode(next); | |
| 278 current.children.add(child); | |
| 279 } | |
| 280 current = child; | |
| 281 for (var nextChild in next.children) { | |
| 282 _appendTree(current, nextChild); | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 /// Add path from root to [child], [child], and all of [child]'s sub-trees | |
| 287 /// to filtered tree. | |
| 288 _addTree(CallTreeNode child) { | |
| 289 var current = _addCurrentPath(); | |
| 290 _appendTree(current, child); | |
| 291 } | |
| 292 | |
| 293 /// Descend further into the tree. [current] is from the unfiltered tree. | |
| 294 _descend(CallTreeNode current) { | |
| 295 if (current == null) { | |
| 296 return; | |
| 297 } | |
| 298 _currentPath.add(current); | |
| 299 | |
| 300 if (filter(current)) { | |
| 301 // Filter matched. | |
| 302 if (current.children.length == 0) { | |
| 303 // Have no children. Add this path. | |
| 304 _addTree(null); | |
| 305 } else { | |
| 306 // Add all child trees. | |
| 307 for (var child in current.children) { | |
| 308 _addTree(child); | |
| 309 } | |
| 310 } | |
| 311 } else { | |
| 312 // Did not match, descend to each child. | |
| 313 for (var child in current.children) { | |
| 314 _descend(child); | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 var last = _currentPath.removeLast(); | |
| 319 assert(current == last); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 class _FilteredFunctionCallTreeBuilder extends _FilteredCallTreeBuilder { | |
| 324 _FilteredFunctionCallTreeBuilder( | |
| 325 CallTreeNodeFilter filter, FunctionCallTree tree) | |
| 326 : super( | |
| 327 filter, | |
| 328 tree, | |
| 329 new FunctionCallTree( | |
| 330 tree.inclusive, | |
| 331 new FunctionCallTreeNode( | |
| 332 tree.root.profileData, | |
| 333 tree.root.count, | |
| 334 tree.root.inclusiveNativeAllocations, | |
| 335 tree.root.exclusiveNativeAllocations))); | |
| 336 | |
| 337 _copyNode(FunctionCallTreeNode node) { | |
| 338 return new FunctionCallTreeNode(node.profileData, node.count, | |
| 339 node.inclusiveNativeAllocations, node.exclusiveNativeAllocations); | |
| 340 } | |
| 341 } | |
| 342 | |
| 343 class _FilteredCodeCallTreeBuilder extends _FilteredCallTreeBuilder { | |
| 344 _FilteredCodeCallTreeBuilder(CallTreeNodeFilter filter, CodeCallTree tree) | |
| 345 : super( | |
| 346 filter, | |
| 347 tree, | |
| 348 new CodeCallTree( | |
| 349 tree.inclusive, | |
| 350 new CodeCallTreeNode( | |
| 351 tree.root.profileData, | |
| 352 tree.root.count, | |
| 353 tree.root.inclusiveNativeAllocations, | |
| 354 tree.root.exclusiveNativeAllocations))); | |
| 355 | |
| 356 _copyNode(CodeCallTreeNode node) { | |
| 357 return new CodeCallTreeNode(node.profileData, node.count, | |
| 358 node.inclusiveNativeAllocations, node.exclusiveNativeAllocations); | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 class FunctionCallTree extends CallTree implements M.FunctionCallTree { | |
| 363 FunctionCallTree(bool inclusive, FunctionCallTreeNode root) | |
| 364 : super(inclusive, root) { | |
| 365 if ((root.inclusiveNativeAllocations != null) && | |
| 366 (root.inclusiveNativeAllocations != 0)) { | |
| 367 _setFunctionMemoryPercentage(null, root); | |
| 368 } else { | |
| 369 _setFunctionPercentage(null, root); | |
| 370 } | |
| 371 } | |
| 372 | |
| 373 FunctionCallTree filtered(CallTreeNodeFilter filter) { | |
| 374 var treeFilter = new _FilteredFunctionCallTreeBuilder(filter, this); | |
| 375 treeFilter.build(); | |
| 376 if ((treeFilter.filtered.root.inclusiveNativeAllocations != null) && | |
| 377 (treeFilter.filtered.root.inclusiveNativeAllocations != 0)) { | |
| 378 _setFunctionMemoryPercentage(null, treeFilter.filtered.root); | |
| 379 } else { | |
| 380 _setFunctionPercentage(null, treeFilter.filtered.root); | |
| 381 } | |
| 382 return treeFilter.filtered; | |
| 383 } | |
| 384 | |
| 385 void _setFunctionPercentage( | |
| 386 FunctionCallTreeNode parent, FunctionCallTreeNode node) { | |
| 387 assert(node != null); | |
| 388 var parentPercentage = 1.0; | |
| 389 var parentCount = node.count; | |
| 390 if (parent != null) { | |
| 391 parentPercentage = parent._percentage; | |
| 392 parentCount = parent.count; | |
| 393 } | |
| 394 if (inclusive) { | |
| 395 node._percentage = parentPercentage * (node.count / parentCount); | |
| 396 } else { | |
| 397 node._percentage = (node.count / parentCount); | |
| 398 } | |
| 399 for (var child in node.children) { | |
| 400 _setFunctionPercentage(node, child); | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 void _setFunctionMemoryPercentage( | |
| 405 FunctionCallTreeNode parent, FunctionCallTreeNode node) { | |
| 406 assert(node != null); | |
| 407 var parentPercentage = 1.0; | |
| 408 var parentMemory = node.inclusiveNativeAllocations; | |
| 409 if (parent != null) { | |
| 410 parentPercentage = parent._percentage; | |
| 411 parentMemory = parent.inclusiveNativeAllocations; | |
| 412 } | |
| 413 if (inclusive) { | |
| 414 node._percentage = | |
| 415 parentPercentage * (node.inclusiveNativeAllocations / parentMemory); | |
| 416 } else { | |
| 417 node._percentage = (node.inclusiveNativeAllocations / parentMemory); | |
| 418 } | |
| 419 for (var child in node.children) { | |
| 420 _setFunctionMemoryPercentage(node, child); | |
| 421 } | |
| 422 node.children.sort((a, b) { | |
| 423 return b.inclusiveNativeAllocations - a.inclusiveNativeAllocations; | |
| 424 }); | |
| 425 } | |
| 426 | |
| 427 _markFunctionCallsInner( | |
| 428 FunctionCallTreeNode caller, FunctionCallTreeNode callee) { | |
| 429 if (caller != null) { | |
| 430 caller.profileFunction | |
| 431 ._recordCallee(callee.profileFunction, callee.count); | |
| 432 callee.profileFunction | |
| 433 ._recordCaller(caller.profileFunction, caller.count); | |
| 434 } | |
| 435 for (var child in callee.children) { | |
| 436 _markFunctionCallsInner(callee, child); | |
| 437 } | |
| 438 } | |
| 439 | |
| 440 _markFunctionCalls() { | |
| 441 for (var child in root.children) { | |
| 442 _markFunctionCallsInner(null, child); | |
| 443 } | |
| 444 } | |
| 445 } | |
| 446 | |
| 447 class CodeTick { | |
| 448 final int exclusiveTicks; | |
| 449 final int inclusiveTicks; | |
| 450 CodeTick(this.exclusiveTicks, this.inclusiveTicks); | |
| 451 } | |
| 452 | |
| 453 class InlineIntervalTick { | |
| 454 final int startAddress; | |
| 455 int _inclusiveTicks = 0; | |
| 456 int get inclusiveTicks => _inclusiveTicks; | |
| 457 int _exclusiveTicks = 0; | |
| 458 int get exclusiveTicks => _exclusiveTicks; | |
| 459 InlineIntervalTick(this.startAddress); | |
| 460 } | |
| 461 | |
| 462 class ProfileCode implements M.ProfileCode { | |
| 463 final CpuProfile profile; | |
| 464 final Code code; | |
| 465 int exclusiveTicks; | |
| 466 int inclusiveTicks; | |
| 467 int exclusiveNativeAllocations; | |
| 468 int inclusiveNativeAllocations; | |
| 469 double normalizedExclusiveTicks = 0.0; | |
| 470 double normalizedInclusiveTicks = 0.0; | |
| 471 final addressTicks = new Map<int, CodeTick>(); | |
| 472 final intervalTicks = new Map<int, InlineIntervalTick>(); | |
| 473 String formattedInclusiveTicks = ''; | |
| 474 String formattedExclusiveTicks = ''; | |
| 475 String formattedExclusivePercent = ''; | |
| 476 String formattedCpuTime = ''; | |
| 477 String formattedOnStackTime = ''; | |
| 478 final Set<String> attributes = new Set<String>(); | |
| 479 final Map<ProfileCode, int> callers = new Map<ProfileCode, int>(); | |
| 480 final Map<ProfileCode, int> callees = new Map<ProfileCode, int>(); | |
| 481 | |
| 482 void _processTicks(List<dynamic> profileTicks) { | |
| 483 assert(profileTicks != null); | |
| 484 assert((profileTicks.length % 3) == 0); | |
| 485 for (var i = 0; i < profileTicks.length; i += 3) { | |
| 486 // TODO(observatory): Address is not necessarily representable as a JS | |
| 487 // integer. | |
| 488 var address = int.parse(profileTicks[i] as String, radix: 16); | |
| 489 var exclusive = profileTicks[i + 1] as int; | |
| 490 var inclusive = profileTicks[i + 2] as int; | |
| 491 var tick = new CodeTick(exclusive, inclusive); | |
| 492 addressTicks[address] = tick; | |
| 493 | |
| 494 var interval = code.findInterval(address); | |
| 495 if (interval != null) { | |
| 496 var intervalTick = intervalTicks[interval.start]; | |
| 497 if (intervalTick == null) { | |
| 498 // Insert into map. | |
| 499 intervalTick = new InlineIntervalTick(interval.start); | |
| 500 intervalTicks[interval.start] = intervalTick; | |
| 501 } | |
| 502 intervalTick._inclusiveTicks += inclusive; | |
| 503 intervalTick._exclusiveTicks += exclusive; | |
| 504 } | |
| 505 } | |
| 506 } | |
| 507 | |
| 508 ProfileCode.fromMap(this.profile, this.code, Map data) { | |
| 509 assert(profile != null); | |
| 510 assert(code != null); | |
| 511 | |
| 512 code.profile = this; | |
| 513 | |
| 514 if (code.kind == M.CodeKind.stub) { | |
| 515 attributes.add('stub'); | |
| 516 } else if (code.kind == M.CodeKind.dart) { | |
| 517 if (code.isNative) { | |
| 518 attributes.add('ffi'); // Not to be confused with a C function. | |
| 519 } else { | |
| 520 attributes.add('dart'); | |
| 521 } | |
| 522 if (code.hasIntrinsic) { | |
| 523 attributes.add('intrinsic'); | |
| 524 } | |
| 525 if (code.isOptimized) { | |
| 526 attributes.add('optimized'); | |
| 527 } else { | |
| 528 attributes.add('unoptimized'); | |
| 529 } | |
| 530 } else if (code.kind == M.CodeKind.tag) { | |
| 531 attributes.add('tag'); | |
| 532 } else if (code.kind == M.CodeKind.native) { | |
| 533 attributes.add('native'); | |
| 534 } | |
| 535 inclusiveTicks = data['inclusiveTicks']; | |
| 536 exclusiveTicks = data['exclusiveTicks']; | |
| 537 | |
| 538 normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount; | |
| 539 | |
| 540 normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount; | |
| 541 | |
| 542 var ticks = data['ticks']; | |
| 543 if (ticks != null) { | |
| 544 _processTicks(ticks); | |
| 545 } | |
| 546 | |
| 547 if (data.containsKey('exclusiveNativeAllocations') && | |
| 548 data.containsKey('inclusiveNativeAllocations')) { | |
| 549 exclusiveNativeAllocations = | |
| 550 int.parse(data['exclusiveNativeAllocations']); | |
| 551 inclusiveNativeAllocations = | |
| 552 int.parse(data['inclusiveNativeAllocations']); | |
| 553 } | |
| 554 | |
| 555 formattedExclusivePercent = | |
| 556 Utils.formatPercent(exclusiveTicks, profile.sampleCount); | |
| 557 | |
| 558 formattedCpuTime = Utils.formatTimeMilliseconds( | |
| 559 profile.approximateMillisecondsForCount(exclusiveTicks)); | |
| 560 | |
| 561 formattedOnStackTime = Utils.formatTimeMilliseconds( | |
| 562 profile.approximateMillisecondsForCount(inclusiveTicks)); | |
| 563 | |
| 564 formattedInclusiveTicks = | |
| 565 '${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} ' | |
| 566 '($inclusiveTicks)'; | |
| 567 | |
| 568 formattedExclusiveTicks = | |
| 569 '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} ' | |
| 570 '($exclusiveTicks)'; | |
| 571 } | |
| 572 | |
| 573 _recordCaller(ProfileCode caller, int count) { | |
| 574 var r = callers[caller]; | |
| 575 if (r == null) { | |
| 576 r = 0; | |
| 577 } | |
| 578 callers[caller] = r + count; | |
| 579 } | |
| 580 | |
| 581 _recordCallee(ProfileCode callee, int count) { | |
| 582 var r = callees[callee]; | |
| 583 if (r == null) { | |
| 584 r = 0; | |
| 585 } | |
| 586 callees[callee] = r + count; | |
| 587 } | |
| 588 } | |
| 589 | |
| 590 class ProfileFunction implements M.ProfileFunction { | |
| 591 final CpuProfile profile; | |
| 592 final ServiceFunction function; | |
| 593 // List of compiled code objects containing this function. | |
| 594 final List<ProfileCode> profileCodes = new List<ProfileCode>(); | |
| 595 final Map<ProfileFunction, int> callers = new Map<ProfileFunction, int>(); | |
| 596 final Map<ProfileFunction, int> callees = new Map<ProfileFunction, int>(); | |
| 597 | |
| 598 // Absolute ticks: | |
| 599 int exclusiveTicks = 0; | |
| 600 int inclusiveTicks = 0; | |
| 601 | |
| 602 // Global percentages: | |
| 603 double normalizedExclusiveTicks = 0.0; | |
| 604 double normalizedInclusiveTicks = 0.0; | |
| 605 | |
| 606 // Native allocations: | |
| 607 int exclusiveNativeAllocations = 0; | |
| 608 int inclusiveNativeAllocations = 0; | |
| 609 | |
| 610 String formattedInclusiveTicks = ''; | |
| 611 String formattedExclusiveTicks = ''; | |
| 612 String formattedExclusivePercent = ''; | |
| 613 String formattedCpuTime = ''; | |
| 614 String formattedOnStackTime = ''; | |
| 615 final Set<String> attributes = new Set<String>(); | |
| 616 | |
| 617 int _sortCodes(ProfileCode a, ProfileCode b) { | |
| 618 if (a.code.isOptimized == b.code.isOptimized) { | |
| 619 return b.code.profile.exclusiveTicks - a.code.profile.exclusiveTicks; | |
| 620 } | |
| 621 if (a.code.isOptimized) { | |
| 622 return -1; | |
| 623 } | |
| 624 return 1; | |
| 625 } | |
| 626 | |
| 627 // Does this function have an optimized version of itself? | |
| 628 bool hasOptimizedCode() { | |
| 629 for (var profileCode in profileCodes) { | |
| 630 if (profileCode.code.function != function) { | |
| 631 continue; | |
| 632 } | |
| 633 if (profileCode.code.isOptimized) { | |
| 634 return true; | |
| 635 } | |
| 636 } | |
| 637 return false; | |
| 638 } | |
| 639 | |
| 640 // Does this function have an unoptimized version of itself? | |
| 641 bool hasUnoptimizedCode() { | |
| 642 for (var profileCode in profileCodes) { | |
| 643 if (profileCode.code.kind == M.CodeKind.stub) { | |
| 644 continue; | |
| 645 } | |
| 646 if (!profileCode.code.isDartCode) { | |
| 647 continue; | |
| 648 } | |
| 649 if (!profileCode.code.isOptimized) { | |
| 650 return true; | |
| 651 } | |
| 652 } | |
| 653 return false; | |
| 654 } | |
| 655 | |
| 656 // Has this function been inlined in another function? | |
| 657 bool isInlined() { | |
| 658 for (var profileCode in profileCodes) { | |
| 659 if (profileCode.code.kind == M.CodeKind.stub) { | |
| 660 continue; | |
| 661 } | |
| 662 if (!profileCode.code.isDartCode) { | |
| 663 continue; | |
| 664 } | |
| 665 // If the code's function isn't this function. | |
| 666 if (profileCode.code.function != function) { | |
| 667 return true; | |
| 668 } | |
| 669 } | |
| 670 return false; | |
| 671 } | |
| 672 | |
| 673 void _addKindBasedAttributes(Set<String> attribs) { | |
| 674 if (function.kind == M.FunctionKind.tag) { | |
| 675 attribs.add('tag'); | |
| 676 } else if (function.kind == M.FunctionKind.stub) { | |
| 677 attribs.add('stub'); | |
| 678 } else if (function.kind == M.FunctionKind.native) { | |
| 679 attribs.add('native'); | |
| 680 } else if (M.isSyntheticFunction(function.kind)) { | |
| 681 attribs.add('synthetic'); | |
| 682 } else if (function.isNative) { | |
| 683 attribs.add('ffi'); // Not to be confused with a C function. | |
| 684 } else { | |
| 685 attribs.add('dart'); | |
| 686 } | |
| 687 if (function.hasIntrinsic == true) { | |
| 688 attribs.add('intrinsic'); | |
| 689 } | |
| 690 } | |
| 691 | |
| 692 ProfileFunction.fromMap(this.profile, this.function, Map data) { | |
| 693 function.profile = this; | |
| 694 for (var codeIndex in data['codes']) { | |
| 695 var profileCode = profile.codes[codeIndex]; | |
| 696 profileCodes.add(profileCode); | |
| 697 } | |
| 698 profileCodes.sort(_sortCodes); | |
| 699 | |
| 700 _addKindBasedAttributes(attributes); | |
| 701 exclusiveTicks = data['exclusiveTicks']; | |
| 702 inclusiveTicks = data['inclusiveTicks']; | |
| 703 | |
| 704 normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount; | |
| 705 normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount; | |
| 706 | |
| 707 if (data.containsKey('exclusiveNativeAllocations') && | |
| 708 data.containsKey('inclusiveNativeAllocations')) { | |
| 709 exclusiveNativeAllocations = | |
| 710 int.parse(data['exclusiveNativeAllocations']); | |
| 711 inclusiveNativeAllocations = | |
| 712 int.parse(data['inclusiveNativeAllocations']); | |
| 713 } | |
| 714 | |
| 715 formattedExclusivePercent = | |
| 716 Utils.formatPercent(exclusiveTicks, profile.sampleCount); | |
| 717 | |
| 718 formattedCpuTime = Utils.formatTimeMilliseconds( | |
| 719 profile.approximateMillisecondsForCount(exclusiveTicks)); | |
| 720 | |
| 721 formattedOnStackTime = Utils.formatTimeMilliseconds( | |
| 722 profile.approximateMillisecondsForCount(inclusiveTicks)); | |
| 723 | |
| 724 formattedInclusiveTicks = | |
| 725 '${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} ' | |
| 726 '($inclusiveTicks)'; | |
| 727 | |
| 728 formattedExclusiveTicks = | |
| 729 '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} ' | |
| 730 '($exclusiveTicks)'; | |
| 731 } | |
| 732 | |
| 733 _recordCaller(ProfileFunction caller, int count) { | |
| 734 var r = callers[caller]; | |
| 735 if (r == null) { | |
| 736 r = 0; | |
| 737 } | |
| 738 callers[caller] = r + count; | |
| 739 } | |
| 740 | |
| 741 _recordCallee(ProfileFunction callee, int count) { | |
| 742 var r = callees[callee]; | |
| 743 if (r == null) { | |
| 744 r = 0; | |
| 745 } | |
| 746 callees[callee] = r + count; | |
| 747 } | |
| 748 } | |
| 749 | |
| 750 // TODO(johnmccutchan): Rename to SampleProfile | |
| 751 class CpuProfile extends M.SampleProfile { | |
| 752 Isolate isolate; | |
| 753 | |
| 754 int sampleCount = 0; | |
| 755 int samplePeriod = 0; | |
| 756 double sampleRate = 0.0; | |
| 757 | |
| 758 int stackDepth = 0; | |
| 759 | |
| 760 double timeSpan = 0.0; | |
| 761 | |
| 762 final Map<String, List> tries = <String, List>{}; | |
| 763 final List<ProfileCode> codes = new List<ProfileCode>(); | |
| 764 bool _builtCodeCalls = false; | |
| 765 final List<ProfileFunction> functions = new List<ProfileFunction>(); | |
| 766 bool _builtFunctionCalls = false; | |
| 767 | |
| 768 CodeCallTree loadCodeTree(M.ProfileTreeDirection direction) { | |
| 769 switch (direction) { | |
| 770 case M.ProfileTreeDirection.inclusive: | |
| 771 return _loadCodeTree(true, tries['inclusiveCodeTrie']); | |
| 772 case M.ProfileTreeDirection.exclusive: | |
| 773 return _loadCodeTree(false, tries['exclusiveCodeTrie']); | |
| 774 } | |
| 775 throw new Exception('Unknown ProfileTreeDirection'); | |
| 776 } | |
| 777 | |
| 778 FunctionCallTree loadFunctionTree(M.ProfileTreeDirection direction) { | |
| 779 switch (direction) { | |
| 780 case M.ProfileTreeDirection.inclusive: | |
| 781 return _loadFunctionTree(true, tries['inclusiveFunctionTrie']); | |
| 782 case M.ProfileTreeDirection.exclusive: | |
| 783 return _loadFunctionTree(false, tries['exclusiveFunctionTrie']); | |
| 784 } | |
| 785 throw new Exception('Unknown ProfileTreeDirection'); | |
| 786 } | |
| 787 | |
| 788 buildCodeCallerAndCallees() { | |
| 789 if (_builtCodeCalls) { | |
| 790 return; | |
| 791 } | |
| 792 _builtCodeCalls = true; | |
| 793 var tree = loadCodeTree(M.ProfileTreeDirection.inclusive); | |
| 794 tree._recordCallerAndCallees(); | |
| 795 } | |
| 796 | |
| 797 buildFunctionCallerAndCallees() { | |
| 798 if (_builtFunctionCalls) { | |
| 799 return; | |
| 800 } | |
| 801 _builtFunctionCalls = true; | |
| 802 var tree = loadFunctionTree(M.ProfileTreeDirection.inclusive); | |
| 803 tree._markFunctionCalls(); | |
| 804 } | |
| 805 | |
| 806 clear() { | |
| 807 sampleCount = 0; | |
| 808 samplePeriod = 0; | |
| 809 sampleRate = 0.0; | |
| 810 stackDepth = 0; | |
| 811 timeSpan = 0.0; | |
| 812 codes.clear(); | |
| 813 functions.clear(); | |
| 814 tries.clear(); | |
| 815 _builtCodeCalls = false; | |
| 816 _builtFunctionCalls = false; | |
| 817 } | |
| 818 | |
| 819 Future load(ServiceObjectOwner owner, ServiceMap profile) async { | |
| 820 await loadProgress(owner, profile).last; | |
| 821 } | |
| 822 | |
| 823 static Future sleep([Duration duration = const Duration(microseconds: 0)]) { | |
| 824 final Completer completer = new Completer(); | |
| 825 new Timer(duration, () => completer.complete()); | |
| 826 return completer.future; | |
| 827 } | |
| 828 | |
| 829 Stream<double> loadProgress(ServiceObjectOwner owner, ServiceMap profile) { | |
| 830 var progress = new StreamController<double>.broadcast(); | |
| 831 | |
| 832 (() async { | |
| 833 final Stopwatch watch = new Stopwatch(); | |
| 834 watch.start(); | |
| 835 int count = 0; | |
| 836 var needToUpdate = () { | |
| 837 count++; | |
| 838 if (((count % 256) == 0) && (watch.elapsedMilliseconds > 16)) { | |
| 839 watch.reset(); | |
| 840 return true; | |
| 841 } | |
| 842 return false; | |
| 843 }; | |
| 844 var signal = (double p) { | |
| 845 progress.add(p); | |
| 846 return sleep(); | |
| 847 }; | |
| 848 try { | |
| 849 clear(); | |
| 850 progress.add(0.0); | |
| 851 if (profile == null) { | |
| 852 return; | |
| 853 } | |
| 854 | |
| 855 if ((owner != null) && (owner is Isolate)) { | |
| 856 isolate = owner; | |
| 857 isolate.resetCachedProfileData(); | |
| 858 } | |
| 859 | |
| 860 sampleCount = profile['sampleCount']; | |
| 861 samplePeriod = profile['samplePeriod']; | |
| 862 sampleRate = (Duration.MICROSECONDS_PER_SECOND / samplePeriod); | |
| 863 stackDepth = profile['stackDepth']; | |
| 864 timeSpan = profile['timeSpan']; | |
| 865 | |
| 866 num length = profile['codes'].length + profile['functions'].length; | |
| 867 | |
| 868 // Process code table. | |
| 869 for (var codeRegion in profile['codes']) { | |
| 870 if (needToUpdate()) { | |
| 871 await signal(count * 100.0 / length); | |
| 872 } | |
| 873 Code code = codeRegion['code']; | |
| 874 assert(code != null); | |
| 875 codes.add(new ProfileCode.fromMap(this, code, codeRegion)); | |
| 876 } | |
| 877 // Process function table. | |
| 878 for (var profileFunction in profile['functions']) { | |
| 879 if (needToUpdate()) { | |
| 880 await signal(count * 100 / length); | |
| 881 } | |
| 882 ServiceFunction function = profileFunction['function']; | |
| 883 assert(function != null); | |
| 884 functions.add( | |
| 885 new ProfileFunction.fromMap(this, function, profileFunction)); | |
| 886 } | |
| 887 | |
| 888 tries['exclusiveCodeTrie'] = | |
| 889 new Uint32List.fromList(profile['exclusiveCodeTrie']); | |
| 890 tries['inclusiveCodeTrie'] = | |
| 891 new Uint32List.fromList(profile['inclusiveCodeTrie']); | |
| 892 tries['exclusiveFunctionTrie'] = | |
| 893 new Uint32List.fromList(profile['exclusiveFunctionTrie']); | |
| 894 tries['inclusiveFunctionTrie'] = | |
| 895 new Uint32List.fromList(profile['inclusiveFunctionTrie']); | |
| 896 } finally { | |
| 897 progress.close(); | |
| 898 } | |
| 899 }()); | |
| 900 return progress.stream; | |
| 901 } | |
| 902 | |
| 903 // Data shared across calls to _read*TrieNode. | |
| 904 int _dataCursor = 0; | |
| 905 | |
| 906 // The code trie is serialized as a list of integers. Each node | |
| 907 // is recreated by consuming some portion of the list. The format is as | |
| 908 // follows: | |
| 909 // [0] index into codeTable of code object. | |
| 910 // [1] tick count (number of times this stack frame occured). | |
| 911 // [2] child node count | |
| 912 // Reading the trie is done by recursively reading the tree depth-first | |
| 913 // pre-order. | |
| 914 CodeCallTree _loadCodeTree(bool inclusive, List<int> data) { | |
| 915 if (data == null) { | |
| 916 return null; | |
| 917 } | |
| 918 if (data.length < 3) { | |
| 919 // Not enough for root node. | |
| 920 return null; | |
| 921 } | |
| 922 // Read the tree, returns the root node. | |
| 923 var root = _readCodeTrie(data); | |
| 924 return new CodeCallTree(inclusive, root); | |
| 925 } | |
| 926 | |
| 927 CodeCallTreeNode _readCodeTrieNode(List<int> data) { | |
| 928 // Lookup code object. | |
| 929 var codeIndex = data[_dataCursor++]; | |
| 930 var code = codes[codeIndex]; | |
| 931 // Node tick counter. | |
| 932 var count = data[_dataCursor++]; | |
| 933 // Child node count. | |
| 934 var children = data[_dataCursor++]; | |
| 935 // Inclusive native allocations. | |
| 936 var inclusiveNativeAllocations = data[_dataCursor++]; | |
| 937 // Exclusive native allocations. | |
| 938 var exclusiveNativeAllocations = data[_dataCursor++]; | |
| 939 // Create node. | |
| 940 var node = new CodeCallTreeNode( | |
| 941 code, count, inclusiveNativeAllocations, exclusiveNativeAllocations); | |
| 942 node.children.length = children; | |
| 943 return node; | |
| 944 } | |
| 945 | |
| 946 CodeCallTreeNode _readCodeTrie(List<int> data) { | |
| 947 final nodeStack = new List<CodeCallTreeNode>(); | |
| 948 final childIndexStack = new List<int>(); | |
| 949 | |
| 950 _dataCursor = 0; | |
| 951 // Read root. | |
| 952 var root = _readCodeTrieNode(data); | |
| 953 | |
| 954 // Push root onto stack. | |
| 955 if (root.children.length > 0) { | |
| 956 nodeStack.add(root); | |
| 957 childIndexStack.add(0); | |
| 958 } | |
| 959 | |
| 960 while (nodeStack.length > 0) { | |
| 961 var lastIndex = nodeStack.length - 1; | |
| 962 // Pop parent from stack. | |
| 963 var parent = nodeStack[lastIndex]; | |
| 964 var childIndex = childIndexStack[lastIndex]; | |
| 965 | |
| 966 // Read child node. | |
| 967 assert(childIndex < parent.children.length); | |
| 968 var node = _readCodeTrieNode(data); | |
| 969 parent.children[childIndex++] = node; | |
| 970 | |
| 971 // If parent still has children, update child index. | |
| 972 if (childIndex < parent.children.length) { | |
| 973 childIndexStack[lastIndex] = childIndex; | |
| 974 } else { | |
| 975 // Finished processing parent node. | |
| 976 nodeStack.removeLast(); | |
| 977 childIndexStack.removeLast(); | |
| 978 } | |
| 979 | |
| 980 // If node has children, push onto stack. | |
| 981 if (node.children.length > 0) { | |
| 982 nodeStack.add(node); | |
| 983 childIndexStack.add(0); | |
| 984 } | |
| 985 } | |
| 986 | |
| 987 return root; | |
| 988 } | |
| 989 | |
| 990 FunctionCallTree _loadFunctionTree(bool inclusive, List<int> data) { | |
| 991 if (data == null) { | |
| 992 return null; | |
| 993 } | |
| 994 if (data.length < 3) { | |
| 995 // Not enough integers for 1 node. | |
| 996 return null; | |
| 997 } | |
| 998 // Read the tree, returns the root node. | |
| 999 var root = _readFunctionTrie(data); | |
| 1000 return new FunctionCallTree(inclusive, root); | |
| 1001 } | |
| 1002 | |
| 1003 FunctionCallTreeNode _readFunctionTrieNode(List<int> data) { | |
| 1004 // Read index into function table. | |
| 1005 var index = data[_dataCursor++]; | |
| 1006 // Lookup function object. | |
| 1007 var function = functions[index]; | |
| 1008 // Counter. | |
| 1009 var count = data[_dataCursor++]; | |
| 1010 // Inclusive native allocations. | |
| 1011 var inclusiveNativeAllocations = data[_dataCursor++]; | |
| 1012 // Exclusive native allocations. | |
| 1013 var exclusiveNativeAllocations = data[_dataCursor++]; | |
| 1014 // Create node. | |
| 1015 var node = new FunctionCallTreeNode(function, count, | |
| 1016 inclusiveNativeAllocations, exclusiveNativeAllocations); | |
| 1017 // Number of code index / count pairs. | |
| 1018 var codeCount = data[_dataCursor++]; | |
| 1019 node.codes.length = codeCount; | |
| 1020 var totalCodeTicks = 0; | |
| 1021 for (var i = 0; i < codeCount; i++) { | |
| 1022 var codeIndex = data[_dataCursor++]; | |
| 1023 var code = codes[codeIndex]; | |
| 1024 assert(code != null); | |
| 1025 var codeTicks = data[_dataCursor++]; | |
| 1026 totalCodeTicks += codeTicks; | |
| 1027 var nodeCode = new FunctionCallTreeNodeCode(code, codeTicks); | |
| 1028 node.codes[i] = nodeCode; | |
| 1029 } | |
| 1030 node.setCodeAttributes(); | |
| 1031 node._totalCodeTicks = totalCodeTicks; | |
| 1032 // Number of children. | |
| 1033 var childCount = data[_dataCursor++]; | |
| 1034 node.children.length = childCount; | |
| 1035 return node; | |
| 1036 } | |
| 1037 | |
| 1038 FunctionCallTreeNode _readFunctionTrie(List<int> data) { | |
| 1039 final nodeStack = new List<FunctionCallTreeNode>(); | |
| 1040 final childIndexStack = new List<int>(); | |
| 1041 | |
| 1042 _dataCursor = 0; | |
| 1043 | |
| 1044 // Read root. | |
| 1045 var root = _readFunctionTrieNode(data); | |
| 1046 | |
| 1047 // Push root onto stack. | |
| 1048 if (root.children.length > 0) { | |
| 1049 nodeStack.add(root); | |
| 1050 childIndexStack.add(0); | |
| 1051 } | |
| 1052 | |
| 1053 while (nodeStack.length > 0) { | |
| 1054 var lastIndex = nodeStack.length - 1; | |
| 1055 // Pop parent from stack. | |
| 1056 var parent = nodeStack[lastIndex]; | |
| 1057 var childIndex = childIndexStack[lastIndex]; | |
| 1058 | |
| 1059 // Read child node. | |
| 1060 assert(childIndex < parent.children.length); | |
| 1061 var node = _readFunctionTrieNode(data); | |
| 1062 parent.children[childIndex++] = node; | |
| 1063 | |
| 1064 // If parent still has children, update child index. | |
| 1065 if (childIndex < parent.children.length) { | |
| 1066 childIndexStack[lastIndex] = childIndex; | |
| 1067 } else { | |
| 1068 // Finished processing parent node. | |
| 1069 nodeStack.removeLast(); | |
| 1070 childIndexStack.removeLast(); | |
| 1071 } | |
| 1072 | |
| 1073 // If node has children, push onto stack. | |
| 1074 if (node.children.length > 0) { | |
| 1075 nodeStack.add(node); | |
| 1076 childIndexStack.add(0); | |
| 1077 } | |
| 1078 } | |
| 1079 | |
| 1080 return root; | |
| 1081 } | |
| 1082 | |
| 1083 int approximateMillisecondsForCount(count) { | |
| 1084 return (count * samplePeriod) ~/ Duration.MICROSECONDS_PER_MILLISECOND; | |
| 1085 } | |
| 1086 | |
| 1087 double approximateSecondsForCount(count) { | |
| 1088 return (count * samplePeriod) / Duration.MICROSECONDS_PER_SECOND; | |
| 1089 } | |
| 1090 } | |
| OLD | NEW |