1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /** 27 * @constructor 28 * @extends {WebInspector.View} 29 * @param {WebInspector.CPUProfileHeader} profileHeader 30 */ 31 WebInspector.CPUProfileView = function(profileHeader) 32 { 33 WebInspector.View.call(this); 34 35 this.element.addStyleClass("profile-view"); 36 37 this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true); 38 this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true); 39 this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true); 40 this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy); 41 42 var columns = []; 43 columns.push({id: "self", title: WebInspector.UIString("Self"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}); 44 columns.push({id: "total", title: WebInspector.UIString("Total"), width: "72px", sortable: true}); 45 columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true}); 46 47 this.dataGrid = new WebInspector.DataGrid(columns); 48 this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this); 49 this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); 50 this.dataGrid.show(this.element); 51 52 this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this)); 53 54 var options = {}; 55 options[WebInspector.CPUProfileView._TypeFlame] = this.viewSelectComboBox.createOption(WebInspector.UIString("Flame Chart"), "", WebInspector.CPUProfileView._TypeFlame); 56 options[WebInspector.CPUProfileView._TypeHeavy] = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy); 57 options[WebInspector.CPUProfileView._TypeTree] = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree); 58 59 var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame; 60 var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame]; 61 this.viewSelectComboBox.select(option); 62 63 this._statusBarButtonsElement = document.createElement("span"); 64 65 this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item"); 66 this.percentButton.addEventListener("click", this._percentClicked, this); 67 this._statusBarButtonsElement.appendChild(this.percentButton.element); 68 69 this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item"); 70 this.focusButton.setEnabled(false); 71 this.focusButton.addEventListener("click", this._focusClicked, this); 72 this._statusBarButtonsElement.appendChild(this.focusButton.element); 73 74 this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item"); 75 this.excludeButton.setEnabled(false); 76 this.excludeButton.addEventListener("click", this._excludeClicked, this); 77 this._statusBarButtonsElement.appendChild(this.excludeButton.element); 78 79 this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item"); 80 this.resetButton.visible = false; 81 this.resetButton.addEventListener("click", this._resetClicked, this); 82 this._statusBarButtonsElement.appendChild(this.resetButton.element); 83 84 this.profileHead = /** @type {?ProfilerAgent.CPUProfileNode} */ (null); 85 this.profile = profileHeader; 86 87 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30)); 88 89 if (this.profile._profile) // If the profile has been loaded from file then use it. 90 this._processProfileData(this.profile._profile); 91 else 92 ProfilerAgent.getCPUProfile(this.profile.uid, this._getCPUProfileCallback.bind(this)); 93 } 94 95 WebInspector.CPUProfileView._TypeFlame = "Flame"; 96 WebInspector.CPUProfileView._TypeTree = "Tree"; 97 WebInspector.CPUProfileView._TypeHeavy = "Heavy"; 98 99 WebInspector.CPUProfileView.prototype = { 100 /** 101 * @param {!number} timeLeft 102 * @param {!number} timeRight 103 */ 104 selectRange: function(timeLeft, timeRight) 105 { 106 if (!this._flameChart) 107 return; 108 this._flameChart.selectRange(timeLeft, timeRight); 109 }, 110 111 _revealProfilerNode: function(event) 112 { 113 var current = this.profileDataGridTree.children[0]; 114 115 while (current && current.profileNode !== event.data) 116 current = current.traverseNextNode(false, null, false); 117 118 if (current) 119 current.revealAndSelect(); 120 }, 121 122 /** 123 * @param {?Protocol.Error} error 124 * @param {ProfilerAgent.CPUProfile} profile 125 */ 126 _getCPUProfileCallback: function(error, profile) 127 { 128 if (error) 129 return; 130 131 if (!profile.head) { 132 // Profiling was tentatively terminated with the "Clear all profiles." button. 133 return; 134 } 135 136 this._processProfileData(profile); 137 }, 138 139 _processProfileData: function(profile) 140 { 141 this.profileHead = profile.head; 142 this.samples = profile.samples; 143 144 this._calculateTimes(profile); 145 146 if (profile.idleTime) 147 this._injectIdleTimeNode(profile); 148 149 this._assignParentsInProfile(); 150 if (this.samples) 151 this._buildIdToNodeMap(); 152 this._changeView(); 153 this._updatePercentButton(); 154 if (this._flameChart) 155 this._flameChart.update(); 156 }, 157 158 get statusBarItems() 159 { 160 return [this.viewSelectComboBox.element, this._statusBarButtonsElement]; 161 }, 162 163 /** 164 * @return {!WebInspector.ProfileDataGridTree} 165 */ 166 _getBottomUpProfileDataGridTree: function() 167 { 168 if (!this._bottomUpProfileDataGridTree) 169 this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profileHead); 170 return this._bottomUpProfileDataGridTree; 171 }, 172 173 /** 174 * @return {!WebInspector.ProfileDataGridTree} 175 */ 176 _getTopDownProfileDataGridTree: function() 177 { 178 if (!this._topDownProfileDataGridTree) 179 this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profileHead); 180 return this._topDownProfileDataGridTree; 181 }, 182 183 willHide: function() 184 { 185 this._currentSearchResultIndex = -1; 186 }, 187 188 refresh: function() 189 { 190 var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; 191 192 this.dataGrid.rootNode().removeChildren(); 193 194 var children = this.profileDataGridTree.children; 195 var count = children.length; 196 197 for (var index = 0; index < count; ++index) 198 this.dataGrid.rootNode().appendChild(children[index]); 199 200 if (selectedProfileNode) 201 selectedProfileNode.selected = true; 202 }, 203 204 refreshVisibleData: function() 205 { 206 var child = this.dataGrid.rootNode().children[0]; 207 while (child) { 208 child.refresh(); 209 child = child.traverseNextNode(false, null, true); 210 } 211 }, 212 213 refreshShowAsPercents: function() 214 { 215 this._updatePercentButton(); 216 this.refreshVisibleData(); 217 }, 218 219 searchCanceled: function() 220 { 221 if (this._searchResults) { 222 for (var i = 0; i < this._searchResults.length; ++i) { 223 var profileNode = this._searchResults[i].profileNode; 224 225 delete profileNode._searchMatchedSelfColumn; 226 delete profileNode._searchMatchedTotalColumn; 227 delete profileNode._searchMatchedFunctionColumn; 228 229 profileNode.refresh(); 230 } 231 } 232 233 delete this._searchFinishedCallback; 234 this._currentSearchResultIndex = -1; 235 this._searchResults = []; 236 }, 237 238 performSearch: function(query, finishedCallback) 239 { 240 // Call searchCanceled since it will reset everything we need before doing a new search. 241 this.searchCanceled(); 242 243 query = query.trim(); 244 245 if (!query.length) 246 return; 247 248 this._searchFinishedCallback = finishedCallback; 249 250 var greaterThan = (query.startsWith(">")); 251 var lessThan = (query.startsWith("<")); 252 var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1)); 253 var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); 254 var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); 255 var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); 256 257 var queryNumber = parseFloat(query); 258 if (greaterThan || lessThan || equalTo) { 259 if (equalTo && (greaterThan || lessThan)) 260 queryNumber = parseFloat(query.substring(2)); 261 else 262 queryNumber = parseFloat(query.substring(1)); 263 } 264 265 var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); 266 267 // Make equalTo implicitly true if it wasn't specified there is no other operator. 268 if (!isNaN(queryNumber) && !(greaterThan || lessThan)) 269 equalTo = true; 270 271 var matcher = createPlainTextSearchRegex(query, "i"); 272 273 function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) 274 { 275 delete profileDataGridNode._searchMatchedSelfColumn; 276 delete profileDataGridNode._searchMatchedTotalColumn; 277 delete profileDataGridNode._searchMatchedFunctionColumn; 278 279 if (percentUnits) { 280 if (lessThan) { 281 if (profileDataGridNode.selfPercent < queryNumber) 282 profileDataGridNode._searchMatchedSelfColumn = true; 283 if (profileDataGridNode.totalPercent < queryNumber) 284 profileDataGridNode._searchMatchedTotalColumn = true; 285 } else if (greaterThan) { 286 if (profileDataGridNode.selfPercent > queryNumber) 287 profileDataGridNode._searchMatchedSelfColumn = true; 288 if (profileDataGridNode.totalPercent > queryNumber) 289 profileDataGridNode._searchMatchedTotalColumn = true; 290 } 291 292 if (equalTo) { 293 if (profileDataGridNode.selfPercent == queryNumber) 294 profileDataGridNode._searchMatchedSelfColumn = true; 295 if (profileDataGridNode.totalPercent == queryNumber) 296 profileDataGridNode._searchMatchedTotalColumn = true; 297 } 298 } else if (millisecondsUnits || secondsUnits) { 299 if (lessThan) { 300 if (profileDataGridNode.selfTime < queryNumberMilliseconds) 301 profileDataGridNode._searchMatchedSelfColumn = true; 302 if (profileDataGridNode.totalTime < queryNumberMilliseconds) 303 profileDataGridNode._searchMatchedTotalColumn = true; 304 } else if (greaterThan) { 305 if (profileDataGridNode.selfTime > queryNumberMilliseconds) 306 profileDataGridNode._searchMatchedSelfColumn = true; 307 if (profileDataGridNode.totalTime > queryNumberMilliseconds) 308 profileDataGridNode._searchMatchedTotalColumn = true; 309 } 310 311 if (equalTo) { 312 if (profileDataGridNode.selfTime == queryNumberMilliseconds) 313 profileDataGridNode._searchMatchedSelfColumn = true; 314 if (profileDataGridNode.totalTime == queryNumberMilliseconds) 315 profileDataGridNode._searchMatchedTotalColumn = true; 316 } 317 } 318 319 if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher))) 320 profileDataGridNode._searchMatchedFunctionColumn = true; 321 322 if (profileDataGridNode._searchMatchedSelfColumn || 323 profileDataGridNode._searchMatchedTotalColumn || 324 profileDataGridNode._searchMatchedFunctionColumn) 325 { 326 profileDataGridNode.refresh(); 327 return true; 328 } 329 330 return false; 331 } 332 333 var current = this.profileDataGridTree.children[0]; 334 335 while (current) { 336 if (matchesQuery(current)) { 337 this._searchResults.push({ profileNode: current }); 338 } 339 340 current = current.traverseNextNode(false, null, false); 341 } 342 343 finishedCallback(this, this._searchResults.length); 344 }, 345 346 jumpToFirstSearchResult: function() 347 { 348 if (!this._searchResults || !this._searchResults.length) 349 return; 350 this._currentSearchResultIndex = 0; 351 this._jumpToSearchResult(this._currentSearchResultIndex); 352 }, 353 354 jumpToLastSearchResult: function() 355 { 356 if (!this._searchResults || !this._searchResults.length) 357 return; 358 this._currentSearchResultIndex = (this._searchResults.length - 1); 359 this._jumpToSearchResult(this._currentSearchResultIndex); 360 }, 361 362 jumpToNextSearchResult: function() 363 { 364 if (!this._searchResults || !this._searchResults.length) 365 return; 366 if (++this._currentSearchResultIndex >= this._searchResults.length) 367 this._currentSearchResultIndex = 0; 368 this._jumpToSearchResult(this._currentSearchResultIndex); 369 }, 370 371 jumpToPreviousSearchResult: function() 372 { 373 if (!this._searchResults || !this._searchResults.length) 374 return; 375 if (--this._currentSearchResultIndex < 0) 376 this._currentSearchResultIndex = (this._searchResults.length - 1); 377 this._jumpToSearchResult(this._currentSearchResultIndex); 378 }, 379 380 showingFirstSearchResult: function() 381 { 382 return (this._currentSearchResultIndex === 0); 383 }, 384 385 showingLastSearchResult: function() 386 { 387 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); 388 }, 389 390 _jumpToSearchResult: function(index) 391 { 392 var searchResult = this._searchResults[index]; 393 if (!searchResult) 394 return; 395 396 var profileNode = searchResult.profileNode; 397 profileNode.revealAndSelect(); 398 }, 399 400 _ensureFlameChartCreated: function() 401 { 402 if (this._flameChart) 403 return; 404 this._flameChart = new WebInspector.FlameChart(this); 405 this._flameChart.addEventListener(WebInspector.FlameChart.Events.SelectedNode, this._onSelectedNode.bind(this)); 406 }, 407 408 /** 409 * @param {WebInspector.Event} event 410 */ 411 _onSelectedNode: function(event) 412 { 413 var node = event.data; 414 if (!node || !node.scriptId) 415 return; 416 var script = WebInspector.debuggerModel.scriptForId(node.scriptId) 417 if (!script) 418 return; 419 var uiLocation = script.rawLocationToUILocation(node.lineNumber); 420 if (!uiLocation) 421 return; 422 WebInspector.showPanel("scripts").showUISourceCode(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber); 423 }, 424 425 _changeView: function() 426 { 427 if (!this.profile) 428 return; 429 430 switch (this.viewSelectComboBox.selectedOption().value) { 431 case WebInspector.CPUProfileView._TypeFlame: 432 this._ensureFlameChartCreated(); 433 this.dataGrid.detach(); 434 this._flameChart.show(this.element); 435 this._viewType.set(WebInspector.CPUProfileView._TypeFlame); 436 this._statusBarButtonsElement.enableStyleClass("hidden", true); 437 return; 438 case WebInspector.CPUProfileView._TypeTree: 439 this.profileDataGridTree = this._getTopDownProfileDataGridTree(); 440 this._sortProfile(); 441 this._viewType.set(WebInspector.CPUProfileView._TypeTree); 442 break; 443 case WebInspector.CPUProfileView._TypeHeavy: 444 this.profileDataGridTree = this._getBottomUpProfileDataGridTree(); 445 this._sortProfile(); 446 this._viewType.set(WebInspector.CPUProfileView._TypeHeavy); 447 break; 448 } 449 450 this._statusBarButtonsElement.enableStyleClass("hidden", false); 451 452 if (this._flameChart) 453 this._flameChart.detach(); 454 this.dataGrid.show(this.element); 455 456 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 457 return; 458 459 // The current search needs to be performed again. First negate out previous match 460 // count by calling the search finished callback with a negative number of matches. 461 // Then perform the search again the with same query and callback. 462 this._searchFinishedCallback(this, -this._searchResults.length); 463 this.performSearch(this.currentQuery, this._searchFinishedCallback); 464 }, 465 466 _percentClicked: function(event) 467 { 468 var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get(); 469 this.showSelfTimeAsPercent.set(!currentState); 470 this.showTotalTimeAsPercent.set(!currentState); 471 this.showAverageTimeAsPercent.set(!currentState); 472 this.refreshShowAsPercents(); 473 }, 474 475 _updatePercentButton: function() 476 { 477 if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) { 478 this.percentButton.title = WebInspector.UIString("Show absolute total and self times."); 479 this.percentButton.toggled = true; 480 } else { 481 this.percentButton.title = WebInspector.UIString("Show total and self times as percentages."); 482 this.percentButton.toggled = false; 483 } 484 }, 485 486 _focusClicked: function(event) 487 { 488 if (!this.dataGrid.selectedNode) 489 return; 490 491 this.resetButton.visible = true; 492 this.profileDataGridTree.focus(this.dataGrid.selectedNode); 493 this.refresh(); 494 this.refreshVisibleData(); 495 }, 496 497 _excludeClicked: function(event) 498 { 499 var selectedNode = this.dataGrid.selectedNode 500 501 if (!selectedNode) 502 return; 503 504 selectedNode.deselect(); 505 506 this.resetButton.visible = true; 507 this.profileDataGridTree.exclude(selectedNode); 508 this.refresh(); 509 this.refreshVisibleData(); 510 }, 511 512 _resetClicked: function(event) 513 { 514 this.resetButton.visible = false; 515 this.profileDataGridTree.restore(); 516 this._linkifier.reset(); 517 this.refresh(); 518 this.refreshVisibleData(); 519 }, 520 521 _dataGridNodeSelected: function(node) 522 { 523 this.focusButton.setEnabled(true); 524 this.excludeButton.setEnabled(true); 525 }, 526 527 _dataGridNodeDeselected: function(node) 528 { 529 this.focusButton.setEnabled(false); 530 this.excludeButton.setEnabled(false); 531 }, 532 533 _sortProfile: function() 534 { 535 var sortAscending = this.dataGrid.isSortOrderAscending(); 536 var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier(); 537 var sortProperty = { 538 "self": "selfTime", 539 "total": "totalTime", 540 "function": "functionName" 541 }[sortColumnIdentifier]; 542 543 this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); 544 545 this.refresh(); 546 }, 547 548 _mouseDownInDataGrid: function(event) 549 { 550 if (event.detail < 2) 551 return; 552 553 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); 554 if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column"))) 555 return; 556 557 if (cell.hasStyleClass("total-column")) 558 this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get()); 559 else if (cell.hasStyleClass("self-column")) 560 this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get()); 561 else if (cell.hasStyleClass("average-column")) 562 this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get()); 563 564 this.refreshShowAsPercents(); 565 566 event.consume(true); 567 }, 568 569 _calculateTimes: function(profile) 570 { 571 function totalHitCount(node) { 572 var result = node.hitCount; 573 for (var i = 0; i < node.children.length; i++) 574 result += totalHitCount(node.children[i]); 575 return result; 576 } 577 profile.totalHitCount = totalHitCount(profile.head); 578 579 var durationMs = 1000 * profile.endTime - 1000 * profile.startTime; 580 var samplingRate = profile.totalHitCount / durationMs; 581 582 function calculateTimesForNode(node) { 583 node.selfTime = node.hitCount * samplingRate; 584 var totalTime = node.selfTime; 585 for (var i = 0; i < node.children.length; i++) 586 totalTime += calculateTimesForNode(node.children[i]); 587 node.totalTime = totalTime; 588 return totalTime; 589 } 590 calculateTimesForNode(profile.head); 591 }, 592 593 _assignParentsInProfile: function() 594 { 595 var head = this.profileHead; 596 head.parent = null; 597 head.head = null; 598 var nodesToTraverse = [ { parent: head, children: head.children } ]; 599 while (nodesToTraverse.length > 0) { 600 var pair = nodesToTraverse.pop(); 601 var parent = pair.parent; 602 var children = pair.children; 603 var length = children.length; 604 for (var i = 0; i < length; ++i) { 605 children[i].head = head; 606 children[i].parent = parent; 607 if (children[i].children.length > 0) 608 nodesToTraverse.push({ parent: children[i], children: children[i].children }); 609 } 610 } 611 }, 612 613 _buildIdToNodeMap: function() 614 { 615 var idToNode = this._idToNode = {}; 616 var stack = [this.profileHead]; 617 while (stack.length) { 618 var node = stack.pop(); 619 idToNode[node.id] = node; 620 for (var i = 0; i < node.children.length; i++) 621 stack.push(node.children[i]); 622 } 623 624 var topLevelNodes = this.profileHead.children; 625 for (var i = 0; i < topLevelNodes.length; i++) { 626 var node = topLevelNodes[i]; 627 if (node.functionName == "(garbage collector)") { 628 this._gcNode = node; 629 break; 630 } 631 } 632 }, 633 634 /** 635 * @param {ProfilerAgent.CPUProfile} profile 636 */ 637 _injectIdleTimeNode: function(profile) 638 { 639 var idleTime = profile.idleTime; 640 var nodes = profile.head.children; 641 642 var programNode = {selfTime: 0}; 643 for (var i = nodes.length - 1; i >= 0; --i) { 644 if (nodes[i].functionName === "(program)") { 645 programNode = nodes[i]; 646 break; 647 } 648 } 649 var programTime = programNode.selfTime; 650 if (idleTime > programTime) 651 idleTime = programTime; 652 programTime = programTime - idleTime; 653 programNode.selfTime = programTime; 654 programNode.totalTime = programTime; 655 var idleNode = { 656 functionName: "(idle)", 657 url: null, 658 lineNumber: 0, 659 totalTime: idleTime, 660 selfTime: idleTime, 661 callUID: 0, 662 children: [] 663 }; 664 nodes.push(idleNode); 665 }, 666 667 __proto__: WebInspector.View.prototype 668 } 669 670 /** 671 * @constructor 672 * @extends {WebInspector.ProfileType} 673 * @implements {ProfilerAgent.Dispatcher} 674 */ 675 WebInspector.CPUProfileType = function() 676 { 677 WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile")); 678 InspectorBackend.registerProfilerDispatcher(this); 679 this._recording = false; 680 WebInspector.CPUProfileType.instance = this; 681 } 682 683 WebInspector.CPUProfileType.TypeId = "CPU"; 684 685 WebInspector.CPUProfileType.prototype = { 686 /** 687 * @override 688 * @return {string} 689 */ 690 fileExtension: function() 691 { 692 return ".cpuprofile"; 693 }, 694 695 get buttonTooltip() 696 { 697 return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling."); 698 }, 699 700 /** 701 * @override 702 * @return {boolean} 703 */ 704 buttonClicked: function() 705 { 706 if (this._recording) { 707 this.stopRecordingProfile(); 708 return false; 709 } else { 710 this.startRecordingProfile(); 711 return true; 712 } 713 }, 714 715 get treeItemTitle() 716 { 717 return WebInspector.UIString("CPU PROFILES"); 718 }, 719 720 get description() 721 { 722 return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions."); 723 }, 724 725 /** 726 * @param {ProfilerAgent.ProfileHeader} profileHeader 727 */ 728 addProfileHeader: function(profileHeader) 729 { 730 this.addProfile(this.createProfile(profileHeader)); 731 }, 732 733 isRecordingProfile: function() 734 { 735 return this._recording; 736 }, 737 738 startRecordingProfile: function() 739 { 740 this._recording = true; 741 WebInspector.userMetrics.ProfilesCPUProfileTaken.record(); 742 ProfilerAgent.start(); 743 }, 744 745 stopRecordingProfile: function() 746 { 747 this._recording = false; 748 ProfilerAgent.stop(); 749 }, 750 751 /** 752 * @param {boolean} isProfiling 753 */ 754 setRecordingProfile: function(isProfiling) 755 { 756 this._recording = isProfiling; 757 }, 758 759 /** 760 * @override 761 * @param {string=} title 762 * @return {!WebInspector.ProfileHeader} 763 */ 764 createTemporaryProfile: function(title) 765 { 766 title = title || WebInspector.UIString("Recording\u2026"); 767 return new WebInspector.CPUProfileHeader(this, title); 768 }, 769 770 /** 771 * @override 772 * @param {ProfilerAgent.ProfileHeader} profile 773 * @return {!WebInspector.ProfileHeader} 774 */ 775 createProfile: function(profile) 776 { 777 return new WebInspector.CPUProfileHeader(this, profile.title, profile.uid); 778 }, 779 780 /** 781 * @override 782 * @param {!WebInspector.ProfileHeader} profile 783 */ 784 removeProfile: function(profile) 785 { 786 WebInspector.ProfileType.prototype.removeProfile.call(this, profile); 787 if (!profile.isTemporary) 788 ProfilerAgent.removeProfile(this.id, profile.uid); 789 }, 790 791 /** 792 * @override 793 * @param {function(this:WebInspector.ProfileType, ?string, Array.<ProfilerAgent.ProfileHeader>)} populateCallback 794 */ 795 _requestProfilesFromBackend: function(populateCallback) 796 { 797 ProfilerAgent.getProfileHeaders(populateCallback); 798 }, 799 800 /** 801 * @override 802 */ 803 resetProfiles: function() 804 { 805 this._reset(); 806 }, 807 808 /** @deprecated To be removed from the protocol */ 809 addHeapSnapshotChunk: function(uid, chunk) 810 { 811 throw new Error("Never called"); 812 }, 813 814 /** @deprecated To be removed from the protocol */ 815 finishHeapSnapshot: function(uid) 816 { 817 throw new Error("Never called"); 818 }, 819 820 /** @deprecated To be removed from the protocol */ 821 reportHeapSnapshotProgress: function(done, total) 822 { 823 throw new Error("Never called"); 824 }, 825 826 __proto__: WebInspector.ProfileType.prototype 827 } 828 829 /** 830 * @constructor 831 * @extends {WebInspector.ProfileHeader} 832 * @implements {WebInspector.OutputStream} 833 * @implements {WebInspector.OutputStreamDelegate} 834 * @param {!WebInspector.CPUProfileType} type 835 * @param {string} title 836 * @param {number=} uid 837 */ 838 WebInspector.CPUProfileHeader = function(type, title, uid) 839 { 840 WebInspector.ProfileHeader.call(this, type, title, uid); 841 } 842 843 WebInspector.CPUProfileHeader.prototype = { 844 onTransferStarted: function() 845 { 846 this._jsonifiedProfile = ""; 847 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length)); 848 }, 849 850 /** 851 * @param {WebInspector.ChunkedReader} reader 852 */ 853 onChunkTransferred: function(reader) 854 { 855 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length)); 856 }, 857 858 onTransferFinished: function() 859 { 860 861 this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026"); 862 this._profile = JSON.parse(this._jsonifiedProfile); 863 this._jsonifiedProfile = null; 864 this.sidebarElement.subtitle = WebInspector.UIString("Loaded"); 865 this.isTemporary = false; 866 }, 867 868 /** 869 * @param {WebInspector.ChunkedReader} reader 870 */ 871 onError: function(reader, e) 872 { 873 switch(e.target.error.code) { 874 case e.target.error.NOT_FOUND_ERR: 875 this.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName()); 876 break; 877 case e.target.error.NOT_READABLE_ERR: 878 this.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName()); 879 break; 880 case e.target.error.ABORT_ERR: 881 break; 882 default: 883 this.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code); 884 } 885 }, 886 887 /** 888 * @param {string} text 889 */ 890 write: function(text) 891 { 892 this._jsonifiedProfile += text; 893 }, 894 895 close: function() { }, 896 897 /** 898 * @override 899 */ 900 createSidebarTreeElement: function() 901 { 902 return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item"); 903 }, 904 905 /** 906 * @override 907 * @param {WebInspector.ProfilesPanel} profilesPanel 908 */ 909 createView: function(profilesPanel) 910 { 911 return new WebInspector.CPUProfileView(this); 912 }, 913 914 /** 915 * @override 916 * @return {boolean} 917 */ 918 canSaveToFile: function() 919 { 920 return true; 921 }, 922 923 saveToFile: function() 924 { 925 var fileOutputStream = new WebInspector.FileOutputStream(); 926 927 /** 928 * @param {?Protocol.Error} error 929 * @param {ProfilerAgent.CPUProfile} profile 930 */ 931 function getCPUProfileCallback(error, profile) 932 { 933 if (error) { 934 fileOutputStream.close(); 935 return; 936 } 937 938 if (!profile.head) { 939 // Profiling was tentatively terminated with the "Clear all profiles." button. 940 fileOutputStream.close(); 941 return; 942 } 943 944 fileOutputStream.write(JSON.stringify(profile), fileOutputStream.close.bind(fileOutputStream)); 945 } 946 947 function onOpen() 948 { 949 ProfilerAgent.getCPUProfile(this.uid, getCPUProfileCallback.bind(this)); 950 } 951 952 this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); 953 fileOutputStream.open(this._fileName, onOpen.bind(this)); 954 }, 955 956 /** 957 * @param {File} file 958 */ 959 loadFromFile: function(file) 960 { 961 this.title = file.name; 962 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); 963 this.sidebarElement.wait = true; 964 965 var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this); 966 fileReader.start(this); 967 }, 968 969 __proto__: WebInspector.ProfileHeader.prototype 970 } 971