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/** 28 * @constructor 29 * @extends {WebInspector.VBox} 30 * @param {!WebInspector.CPUProfileHeader} profileHeader 31 */ 32WebInspector.CPUProfileView = function(profileHeader) 33{ 34 WebInspector.VBox.call(this); 35 this.element.classList.add("cpu-profile-view"); 36 37 this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy); 38 39 var columns = []; 40 columns.push({id: "self", title: WebInspector.UIString("Self"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}); 41 columns.push({id: "total", title: WebInspector.UIString("Total"), width: "120px", sortable: true}); 42 columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true}); 43 44 this.dataGrid = new WebInspector.DataGrid(columns); 45 this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this); 46 this.dataGrid.show(this.element); 47 48 this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this)); 49 50 var options = {}; 51 options[WebInspector.CPUProfileView._TypeFlame] = this.viewSelectComboBox.createOption(WebInspector.UIString("Chart"), "", WebInspector.CPUProfileView._TypeFlame); 52 options[WebInspector.CPUProfileView._TypeHeavy] = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy); 53 options[WebInspector.CPUProfileView._TypeTree] = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree); 54 55 var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame; 56 var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame]; 57 this.viewSelectComboBox.select(option); 58 59 this._statusBarButtonsElement = document.createElement("span"); 60 61 this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item"); 62 this.focusButton.setEnabled(false); 63 this.focusButton.addEventListener("click", this._focusClicked, this); 64 this._statusBarButtonsElement.appendChild(this.focusButton.element); 65 66 this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item"); 67 this.excludeButton.setEnabled(false); 68 this.excludeButton.addEventListener("click", this._excludeClicked, this); 69 this._statusBarButtonsElement.appendChild(this.excludeButton.element); 70 71 this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item"); 72 this.resetButton.visible = false; 73 this.resetButton.addEventListener("click", this._resetClicked, this); 74 this._statusBarButtonsElement.appendChild(this.resetButton.element); 75 76 this._profileHeader = profileHeader; 77 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30)); 78 79 this.profile = new WebInspector.CPUProfileDataModel(profileHeader._profile || profileHeader.protocolProfile()); 80 81 this._changeView(); 82 if (this._flameChart) 83 this._flameChart.update(); 84} 85 86WebInspector.CPUProfileView._TypeFlame = "Flame"; 87WebInspector.CPUProfileView._TypeTree = "Tree"; 88WebInspector.CPUProfileView._TypeHeavy = "Heavy"; 89 90WebInspector.CPUProfileView.prototype = { 91 /** 92 * @return {?WebInspector.Target} 93 */ 94 target: function() 95 { 96 return this._profileHeader.target(); 97 }, 98 99 /** 100 * @param {!number} timeLeft 101 * @param {!number} timeRight 102 */ 103 selectRange: function(timeLeft, timeRight) 104 { 105 if (!this._flameChart) 106 return; 107 this._flameChart.selectRange(timeLeft, timeRight); 108 }, 109 110 get statusBarItems() 111 { 112 return [this.viewSelectComboBox.element, this._statusBarButtonsElement]; 113 }, 114 115 /** 116 * @return {!WebInspector.ProfileDataGridTree} 117 */ 118 _getBottomUpProfileDataGridTree: function() 119 { 120 if (!this._bottomUpProfileDataGridTree) 121 this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); 122 return this._bottomUpProfileDataGridTree; 123 }, 124 125 /** 126 * @return {!WebInspector.ProfileDataGridTree} 127 */ 128 _getTopDownProfileDataGridTree: function() 129 { 130 if (!this._topDownProfileDataGridTree) 131 this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); 132 return this._topDownProfileDataGridTree; 133 }, 134 135 willHide: function() 136 { 137 this._currentSearchResultIndex = -1; 138 }, 139 140 refresh: function() 141 { 142 var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; 143 144 this.dataGrid.rootNode().removeChildren(); 145 146 var children = this.profileDataGridTree.children; 147 var count = children.length; 148 149 for (var index = 0; index < count; ++index) 150 this.dataGrid.rootNode().appendChild(children[index]); 151 152 if (selectedProfileNode) 153 selectedProfileNode.selected = true; 154 }, 155 156 refreshVisibleData: function() 157 { 158 var child = this.dataGrid.rootNode().children[0]; 159 while (child) { 160 child.refresh(); 161 child = child.traverseNextNode(false, null, true); 162 } 163 }, 164 165 searchCanceled: function() 166 { 167 if (this._searchResults) { 168 for (var i = 0; i < this._searchResults.length; ++i) { 169 var profileNode = this._searchResults[i].profileNode; 170 171 delete profileNode._searchMatchedSelfColumn; 172 delete profileNode._searchMatchedTotalColumn; 173 delete profileNode._searchMatchedFunctionColumn; 174 175 profileNode.refresh(); 176 } 177 } 178 179 delete this._searchFinishedCallback; 180 this._currentSearchResultIndex = -1; 181 this._searchResults = []; 182 }, 183 184 performSearch: function(query, finishedCallback) 185 { 186 // Call searchCanceled since it will reset everything we need before doing a new search. 187 this.searchCanceled(); 188 189 query = query.trim(); 190 191 if (!query.length) 192 return; 193 194 this._searchFinishedCallback = finishedCallback; 195 196 var greaterThan = (query.startsWith(">")); 197 var lessThan = (query.startsWith("<")); 198 var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1)); 199 var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); 200 var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); 201 var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); 202 203 var queryNumber = parseFloat(query); 204 if (greaterThan || lessThan || equalTo) { 205 if (equalTo && (greaterThan || lessThan)) 206 queryNumber = parseFloat(query.substring(2)); 207 else 208 queryNumber = parseFloat(query.substring(1)); 209 } 210 211 var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); 212 213 // Make equalTo implicitly true if it wasn't specified there is no other operator. 214 if (!isNaN(queryNumber) && !(greaterThan || lessThan)) 215 equalTo = true; 216 217 var matcher = createPlainTextSearchRegex(query, "i"); 218 219 function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) 220 { 221 delete profileDataGridNode._searchMatchedSelfColumn; 222 delete profileDataGridNode._searchMatchedTotalColumn; 223 delete profileDataGridNode._searchMatchedFunctionColumn; 224 225 if (percentUnits) { 226 if (lessThan) { 227 if (profileDataGridNode.selfPercent < queryNumber) 228 profileDataGridNode._searchMatchedSelfColumn = true; 229 if (profileDataGridNode.totalPercent < queryNumber) 230 profileDataGridNode._searchMatchedTotalColumn = true; 231 } else if (greaterThan) { 232 if (profileDataGridNode.selfPercent > queryNumber) 233 profileDataGridNode._searchMatchedSelfColumn = true; 234 if (profileDataGridNode.totalPercent > queryNumber) 235 profileDataGridNode._searchMatchedTotalColumn = true; 236 } 237 238 if (equalTo) { 239 if (profileDataGridNode.selfPercent == queryNumber) 240 profileDataGridNode._searchMatchedSelfColumn = true; 241 if (profileDataGridNode.totalPercent == queryNumber) 242 profileDataGridNode._searchMatchedTotalColumn = true; 243 } 244 } else if (millisecondsUnits || secondsUnits) { 245 if (lessThan) { 246 if (profileDataGridNode.selfTime < queryNumberMilliseconds) 247 profileDataGridNode._searchMatchedSelfColumn = true; 248 if (profileDataGridNode.totalTime < queryNumberMilliseconds) 249 profileDataGridNode._searchMatchedTotalColumn = true; 250 } else if (greaterThan) { 251 if (profileDataGridNode.selfTime > queryNumberMilliseconds) 252 profileDataGridNode._searchMatchedSelfColumn = true; 253 if (profileDataGridNode.totalTime > queryNumberMilliseconds) 254 profileDataGridNode._searchMatchedTotalColumn = true; 255 } 256 257 if (equalTo) { 258 if (profileDataGridNode.selfTime == queryNumberMilliseconds) 259 profileDataGridNode._searchMatchedSelfColumn = true; 260 if (profileDataGridNode.totalTime == queryNumberMilliseconds) 261 profileDataGridNode._searchMatchedTotalColumn = true; 262 } 263 } 264 265 if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher))) 266 profileDataGridNode._searchMatchedFunctionColumn = true; 267 268 if (profileDataGridNode._searchMatchedSelfColumn || 269 profileDataGridNode._searchMatchedTotalColumn || 270 profileDataGridNode._searchMatchedFunctionColumn) 271 { 272 profileDataGridNode.refresh(); 273 return true; 274 } 275 276 return false; 277 } 278 279 var current = this.profileDataGridTree.children[0]; 280 281 while (current) { 282 if (matchesQuery(current)) { 283 this._searchResults.push({ profileNode: current }); 284 } 285 286 current = current.traverseNextNode(false, null, false); 287 } 288 289 finishedCallback(this, this._searchResults.length); 290 }, 291 292 jumpToFirstSearchResult: function() 293 { 294 if (!this._searchResults || !this._searchResults.length) 295 return; 296 this._currentSearchResultIndex = 0; 297 this._jumpToSearchResult(this._currentSearchResultIndex); 298 }, 299 300 jumpToLastSearchResult: function() 301 { 302 if (!this._searchResults || !this._searchResults.length) 303 return; 304 this._currentSearchResultIndex = (this._searchResults.length - 1); 305 this._jumpToSearchResult(this._currentSearchResultIndex); 306 }, 307 308 jumpToNextSearchResult: function() 309 { 310 if (!this._searchResults || !this._searchResults.length) 311 return; 312 if (++this._currentSearchResultIndex >= this._searchResults.length) 313 this._currentSearchResultIndex = 0; 314 this._jumpToSearchResult(this._currentSearchResultIndex); 315 }, 316 317 jumpToPreviousSearchResult: function() 318 { 319 if (!this._searchResults || !this._searchResults.length) 320 return; 321 if (--this._currentSearchResultIndex < 0) 322 this._currentSearchResultIndex = (this._searchResults.length - 1); 323 this._jumpToSearchResult(this._currentSearchResultIndex); 324 }, 325 326 /** 327 * @return {boolean} 328 */ 329 showingFirstSearchResult: function() 330 { 331 return (this._currentSearchResultIndex === 0); 332 }, 333 334 /** 335 * @return {boolean} 336 */ 337 showingLastSearchResult: function() 338 { 339 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); 340 }, 341 342 /** 343 * @return {number} 344 */ 345 currentSearchResultIndex: function() { 346 return this._currentSearchResultIndex; 347 }, 348 349 _jumpToSearchResult: function(index) 350 { 351 var searchResult = this._searchResults[index]; 352 if (!searchResult) 353 return; 354 355 var profileNode = searchResult.profileNode; 356 profileNode.revealAndSelect(); 357 }, 358 359 _ensureFlameChartCreated: function() 360 { 361 if (this._flameChart) 362 return; 363 this._dataProvider = new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target()); 364 this._flameChart = new WebInspector.CPUProfileFlameChart(this._dataProvider); 365 this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this)); 366 }, 367 368 /** 369 * @param {!WebInspector.Event} event 370 */ 371 _onEntrySelected: function(event) 372 { 373 var entryIndex = event.data; 374 var node = this._dataProvider._entryNodes[entryIndex]; 375 var target = this._profileHeader.target(); 376 if (!node || !node.scriptId || !target) 377 return; 378 var script = target.debuggerModel.scriptForId(node.scriptId) 379 if (!script) 380 return; 381 var location = /** @type {!WebInspector.DebuggerModel.Location} */ (script.target().debuggerModel.createRawLocation(script, node.lineNumber, 0)); 382 WebInspector.Revealer.reveal(WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(location)); 383 }, 384 385 _changeView: function() 386 { 387 if (!this.profile) 388 return; 389 390 switch (this.viewSelectComboBox.selectedOption().value) { 391 case WebInspector.CPUProfileView._TypeFlame: 392 this._ensureFlameChartCreated(); 393 this.dataGrid.detach(); 394 this._flameChart.show(this.element); 395 this._viewType.set(WebInspector.CPUProfileView._TypeFlame); 396 this._statusBarButtonsElement.classList.toggle("hidden", true); 397 return; 398 case WebInspector.CPUProfileView._TypeTree: 399 this.profileDataGridTree = this._getTopDownProfileDataGridTree(); 400 this._sortProfile(); 401 this._viewType.set(WebInspector.CPUProfileView._TypeTree); 402 break; 403 case WebInspector.CPUProfileView._TypeHeavy: 404 this.profileDataGridTree = this._getBottomUpProfileDataGridTree(); 405 this._sortProfile(); 406 this._viewType.set(WebInspector.CPUProfileView._TypeHeavy); 407 break; 408 } 409 410 this._statusBarButtonsElement.classList.toggle("hidden", false); 411 412 if (this._flameChart) 413 this._flameChart.detach(); 414 this.dataGrid.show(this.element); 415 416 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 417 return; 418 419 // The current search needs to be performed again. First negate out previous match 420 // count by calling the search finished callback with a negative number of matches. 421 // Then perform the search again the with same query and callback. 422 this._searchFinishedCallback(this, -this._searchResults.length); 423 this.performSearch(this.currentQuery, this._searchFinishedCallback); 424 }, 425 426 _focusClicked: function(event) 427 { 428 if (!this.dataGrid.selectedNode) 429 return; 430 431 this.resetButton.visible = true; 432 this.profileDataGridTree.focus(this.dataGrid.selectedNode); 433 this.refresh(); 434 this.refreshVisibleData(); 435 }, 436 437 _excludeClicked: function(event) 438 { 439 var selectedNode = this.dataGrid.selectedNode 440 441 if (!selectedNode) 442 return; 443 444 selectedNode.deselect(); 445 446 this.resetButton.visible = true; 447 this.profileDataGridTree.exclude(selectedNode); 448 this.refresh(); 449 this.refreshVisibleData(); 450 }, 451 452 _resetClicked: function(event) 453 { 454 this.resetButton.visible = false; 455 this.profileDataGridTree.restore(); 456 this._linkifier.reset(); 457 this.refresh(); 458 this.refreshVisibleData(); 459 }, 460 461 _dataGridNodeSelected: function(node) 462 { 463 this.focusButton.setEnabled(true); 464 this.excludeButton.setEnabled(true); 465 }, 466 467 _dataGridNodeDeselected: function(node) 468 { 469 this.focusButton.setEnabled(false); 470 this.excludeButton.setEnabled(false); 471 }, 472 473 _sortProfile: function() 474 { 475 var sortAscending = this.dataGrid.isSortOrderAscending(); 476 var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier(); 477 var sortProperty = { 478 "self": "selfTime", 479 "total": "totalTime", 480 "function": "functionName" 481 }[sortColumnIdentifier]; 482 483 this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); 484 485 this.refresh(); 486 }, 487 488 __proto__: WebInspector.VBox.prototype 489} 490 491/** 492 * @constructor 493 * @extends {WebInspector.ProfileType} 494 */ 495WebInspector.CPUProfileType = function() 496{ 497 WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile")); 498 this._recording = false; 499 500 this._nextAnonymousConsoleProfileNumber = 1; 501 this._anonymousConsoleProfileIdToTitle = {}; 502 503 WebInspector.CPUProfileType.instance = this; 504 WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.EventTypes.ConsoleProfileStarted, this._consoleProfileStarted, this); 505 WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.EventTypes.ConsoleProfileFinished, this._consoleProfileFinished, this); 506} 507 508WebInspector.CPUProfileType.TypeId = "CPU"; 509 510WebInspector.CPUProfileType.prototype = { 511 /** 512 * @override 513 * @return {string} 514 */ 515 fileExtension: function() 516 { 517 return ".cpuprofile"; 518 }, 519 520 get buttonTooltip() 521 { 522 return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling."); 523 }, 524 525 /** 526 * @override 527 * @return {boolean} 528 */ 529 buttonClicked: function() 530 { 531 if (this._recording) { 532 this.stopRecordingProfile(); 533 return false; 534 } else { 535 this.startRecordingProfile(); 536 return true; 537 } 538 }, 539 540 get treeItemTitle() 541 { 542 return WebInspector.UIString("CPU PROFILES"); 543 }, 544 545 get description() 546 { 547 return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions."); 548 }, 549 550 /** 551 * @param {!WebInspector.Event} event 552 */ 553 _consoleProfileStarted: function(event) 554 { 555 var protocolId = /** @type {string} */ (event.data.protocolId); 556 var scriptLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (event.data.scriptLocation); 557 var resolvedTitle = /** @type {string|undefined} */ (event.data.title); 558 if (!resolvedTitle) { 559 resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++); 560 this._anonymousConsoleProfileIdToTitle[protocolId] = resolvedTitle; 561 } 562 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle)); 563 }, 564 565 /** 566 * @param {!WebInspector.Event} event 567 */ 568 _consoleProfileFinished: function(event) 569 { 570 var protocolId = /** @type {string} */ (event.data.protocolId); 571 var scriptLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (event.data.scriptLocation); 572 var cpuProfile = /** @type {!ProfilerAgent.CPUProfile} */ (event.data.cpuProfile); 573 var resolvedTitle = /** @type {string|undefined} */ (event.data.title); 574 if (typeof resolvedTitle === "undefined") { 575 resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId]; 576 delete this._anonymousConsoleProfileIdToTitle[protocolId]; 577 } 578 579 var profile = new WebInspector.CPUProfileHeader(scriptLocation.target(), this, resolvedTitle); 580 profile.setProtocolProfile(cpuProfile); 581 this.addProfile(profile); 582 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle)); 583 }, 584 585 /** 586 * @param {string} type 587 * @param {!WebInspector.DebuggerModel.Location} scriptLocation 588 * @param {string} messageText 589 */ 590 _addMessageToConsole: function(type, scriptLocation, messageText) 591 { 592 var script = scriptLocation.script(); 593 var target = scriptLocation.target(); 594 var message = new WebInspector.ConsoleMessage( 595 target, 596 WebInspector.ConsoleMessage.MessageSource.ConsoleAPI, 597 WebInspector.ConsoleMessage.MessageLevel.Debug, 598 messageText, 599 type, 600 undefined, 601 undefined, 602 undefined, 603 undefined, 604 undefined, 605 [{ 606 functionName: "", 607 scriptId: scriptLocation.scriptId, 608 url: script ? script.contentURL() : "", 609 lineNumber: scriptLocation.lineNumber, 610 columnNumber: scriptLocation.columnNumber || 0 611 }]); 612 613 target.consoleModel.addMessage(message); 614 }, 615 616 startRecordingProfile: function() 617 { 618 var target = WebInspector.context.flavor(WebInspector.Target); 619 if (this._profileBeingRecorded || !target) 620 return; 621 var profile = new WebInspector.CPUProfileHeader(target, this); 622 this.setProfileBeingRecorded(profile); 623 this.addProfile(profile); 624 profile.updateStatus(WebInspector.UIString("Recording\u2026")); 625 this._recording = true; 626 target.cpuProfilerModel.startRecording(); 627 }, 628 629 stopRecordingProfile: function() 630 { 631 this._recording = false; 632 if (!this._profileBeingRecorded || !this._profileBeingRecorded.target()) 633 return; 634 635 /** 636 * @param {?string} error 637 * @param {?ProfilerAgent.CPUProfile} profile 638 * @this {WebInspector.CPUProfileType} 639 */ 640 function didStopProfiling(error, profile) 641 { 642 if (!this._profileBeingRecorded) 643 return; 644 this._profileBeingRecorded.setProtocolProfile(profile); 645 this._profileBeingRecorded.updateStatus(""); 646 var recordedProfile = this._profileBeingRecorded; 647 this.setProfileBeingRecorded(null); 648 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile); 649 } 650 this._profileBeingRecorded.target().cpuProfilerModel.stopRecording(didStopProfiling.bind(this)); 651 }, 652 653 /** 654 * @override 655 * @param {string} title 656 * @return {!WebInspector.ProfileHeader} 657 */ 658 createProfileLoadedFromFile: function(title) 659 { 660 return new WebInspector.CPUProfileHeader(null, this, title); 661 }, 662 663 /** 664 * @override 665 */ 666 profileBeingRecordedRemoved: function() 667 { 668 this.stopRecordingProfile(); 669 }, 670 671 __proto__: WebInspector.ProfileType.prototype 672} 673 674/** 675 * @constructor 676 * @extends {WebInspector.ProfileHeader} 677 * @implements {WebInspector.OutputStream} 678 * @implements {WebInspector.OutputStreamDelegate} 679 * @param {?WebInspector.Target} target 680 * @param {!WebInspector.CPUProfileType} type 681 * @param {string=} title 682 */ 683WebInspector.CPUProfileHeader = function(target, type, title) 684{ 685 WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Profile %d", type.nextProfileUid())); 686 this._tempFile = null; 687} 688 689WebInspector.CPUProfileHeader.prototype = { 690 onTransferStarted: function() 691 { 692 this._jsonifiedProfile = ""; 693 this.updateStatus(WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length)), true); 694 }, 695 696 /** 697 * @param {!WebInspector.ChunkedReader} reader 698 */ 699 onChunkTransferred: function(reader) 700 { 701 this.updateStatus(WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length))); 702 }, 703 704 onTransferFinished: function() 705 { 706 this.updateStatus(WebInspector.UIString("Parsing\u2026"), true); 707 this._profile = JSON.parse(this._jsonifiedProfile); 708 this._jsonifiedProfile = null; 709 this.updateStatus(WebInspector.UIString("Loaded"), false); 710 711 if (this._profileType.profileBeingRecorded() === this) 712 this._profileType.setProfileBeingRecorded(null); 713 }, 714 715 /** 716 * @param {!WebInspector.ChunkedReader} reader 717 * @param {!Event} e 718 */ 719 onError: function(reader, e) 720 { 721 var subtitle; 722 switch(e.target.error.code) { 723 case e.target.error.NOT_FOUND_ERR: 724 subtitle = WebInspector.UIString("'%s' not found.", reader.fileName()); 725 break; 726 case e.target.error.NOT_READABLE_ERR: 727 subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName()); 728 break; 729 case e.target.error.ABORT_ERR: 730 return; 731 default: 732 subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code); 733 } 734 this.updateStatus(subtitle); 735 }, 736 737 /** 738 * @param {string} text 739 */ 740 write: function(text) 741 { 742 this._jsonifiedProfile += text; 743 }, 744 745 close: function() { }, 746 747 /** 748 * @override 749 */ 750 dispose: function() 751 { 752 this.removeTempFile(); 753 }, 754 755 /** 756 * @override 757 * @param {!WebInspector.ProfilesPanel} panel 758 * @return {!WebInspector.ProfileSidebarTreeElement} 759 */ 760 createSidebarTreeElement: function(panel) 761 { 762 return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item"); 763 }, 764 765 /** 766 * @override 767 * @return {!WebInspector.CPUProfileView} 768 */ 769 createView: function() 770 { 771 return new WebInspector.CPUProfileView(this); 772 }, 773 774 /** 775 * @override 776 * @return {boolean} 777 */ 778 canSaveToFile: function() 779 { 780 return !this.fromFile() && this._protocolProfile; 781 }, 782 783 saveToFile: function() 784 { 785 var fileOutputStream = new WebInspector.FileOutputStream(); 786 787 /** 788 * @param {boolean} accepted 789 * @this {WebInspector.CPUProfileHeader} 790 */ 791 function onOpenForSave(accepted) 792 { 793 if (!accepted) 794 return; 795 function didRead(data) 796 { 797 if (data) 798 fileOutputStream.write(data, fileOutputStream.close.bind(fileOutputStream)); 799 else 800 fileOutputStream.close(); 801 } 802 if (this._failedToCreateTempFile) { 803 WebInspector.console.error("Failed to open temp file with heap snapshot"); 804 fileOutputStream.close(); 805 } else if (this._tempFile) { 806 this._tempFile.read(didRead); 807 } else { 808 this._onTempFileReady = onOpenForSave.bind(this, accepted); 809 } 810 } 811 this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); 812 fileOutputStream.open(this._fileName, onOpenForSave.bind(this)); 813 }, 814 815 /** 816 * @param {!File} file 817 */ 818 loadFromFile: function(file) 819 { 820 this.updateStatus(WebInspector.UIString("Loading\u2026"), true); 821 var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this); 822 fileReader.start(this); 823 }, 824 825 826 /** 827 * @return {?ProfilerAgent.CPUProfile} 828 */ 829 protocolProfile: function() 830 { 831 return this._protocolProfile; 832 }, 833 834 /** 835 * @param {!ProfilerAgent.CPUProfile} cpuProfile 836 */ 837 setProtocolProfile: function(cpuProfile) 838 { 839 this._protocolProfile = cpuProfile; 840 this._saveProfileDataToTempFile(cpuProfile); 841 if (this.canSaveToFile()) 842 this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived); 843 }, 844 845 /** 846 * @param {!ProfilerAgent.CPUProfile} data 847 */ 848 _saveProfileDataToTempFile: function(data) 849 { 850 var serializedData = JSON.stringify(data); 851 852 /** 853 * @this {WebInspector.CPUProfileHeader} 854 */ 855 function didCreateTempFile(tempFile) 856 { 857 this._writeToTempFile(tempFile, serializedData); 858 } 859 new WebInspector.TempFile("cpu-profiler", String(this.uid), didCreateTempFile.bind(this)); 860 }, 861 862 /** 863 * @param {?WebInspector.TempFile} tempFile 864 * @param {string} serializedData 865 */ 866 _writeToTempFile: function(tempFile, serializedData) 867 { 868 this._tempFile = tempFile; 869 if (!tempFile) { 870 this._failedToCreateTempFile = true; 871 this._notifyTempFileReady(); 872 return; 873 } 874 /** 875 * @param {boolean} success 876 * @this {WebInspector.CPUProfileHeader} 877 */ 878 function didWriteToTempFile(success) 879 { 880 if (!success) 881 this._failedToCreateTempFile = true; 882 tempFile.finishWriting(); 883 this._notifyTempFileReady(); 884 } 885 tempFile.write([serializedData], didWriteToTempFile.bind(this)); 886 }, 887 888 _notifyTempFileReady: function() 889 { 890 if (this._onTempFileReady) { 891 this._onTempFileReady(); 892 this._onTempFileReady = null; 893 } 894 }, 895 896 __proto__: WebInspector.ProfileHeader.prototype 897} 898