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 * @param {!number} timeLeft 93 * @param {!number} timeRight 94 */ 95 selectRange: function(timeLeft, timeRight) 96 { 97 if (!this._flameChart) 98 return; 99 this._flameChart.selectRange(timeLeft, timeRight); 100 }, 101 102 get statusBarItems() 103 { 104 return [this.viewSelectComboBox.element, this._statusBarButtonsElement]; 105 }, 106 107 /** 108 * @return {!WebInspector.ProfileDataGridTree} 109 */ 110 _getBottomUpProfileDataGridTree: function() 111 { 112 if (!this._bottomUpProfileDataGridTree) 113 this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); 114 return this._bottomUpProfileDataGridTree; 115 }, 116 117 /** 118 * @return {!WebInspector.ProfileDataGridTree} 119 */ 120 _getTopDownProfileDataGridTree: function() 121 { 122 if (!this._topDownProfileDataGridTree) 123 this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); 124 return this._topDownProfileDataGridTree; 125 }, 126 127 willHide: function() 128 { 129 this._currentSearchResultIndex = -1; 130 }, 131 132 refresh: function() 133 { 134 var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; 135 136 this.dataGrid.rootNode().removeChildren(); 137 138 var children = this.profileDataGridTree.children; 139 var count = children.length; 140 141 for (var index = 0; index < count; ++index) 142 this.dataGrid.rootNode().appendChild(children[index]); 143 144 if (selectedProfileNode) 145 selectedProfileNode.selected = true; 146 }, 147 148 refreshVisibleData: function() 149 { 150 var child = this.dataGrid.rootNode().children[0]; 151 while (child) { 152 child.refresh(); 153 child = child.traverseNextNode(false, null, true); 154 } 155 }, 156 157 searchCanceled: function() 158 { 159 if (this._searchResults) { 160 for (var i = 0; i < this._searchResults.length; ++i) { 161 var profileNode = this._searchResults[i].profileNode; 162 163 delete profileNode._searchMatchedSelfColumn; 164 delete profileNode._searchMatchedTotalColumn; 165 delete profileNode._searchMatchedFunctionColumn; 166 167 profileNode.refresh(); 168 } 169 } 170 171 delete this._searchFinishedCallback; 172 this._currentSearchResultIndex = -1; 173 this._searchResults = []; 174 }, 175 176 performSearch: function(query, finishedCallback) 177 { 178 // Call searchCanceled since it will reset everything we need before doing a new search. 179 this.searchCanceled(); 180 181 query = query.trim(); 182 183 if (!query.length) 184 return; 185 186 this._searchFinishedCallback = finishedCallback; 187 188 var greaterThan = (query.startsWith(">")); 189 var lessThan = (query.startsWith("<")); 190 var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1)); 191 var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); 192 var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); 193 var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); 194 195 var queryNumber = parseFloat(query); 196 if (greaterThan || lessThan || equalTo) { 197 if (equalTo && (greaterThan || lessThan)) 198 queryNumber = parseFloat(query.substring(2)); 199 else 200 queryNumber = parseFloat(query.substring(1)); 201 } 202 203 var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); 204 205 // Make equalTo implicitly true if it wasn't specified there is no other operator. 206 if (!isNaN(queryNumber) && !(greaterThan || lessThan)) 207 equalTo = true; 208 209 var matcher = createPlainTextSearchRegex(query, "i"); 210 211 function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) 212 { 213 delete profileDataGridNode._searchMatchedSelfColumn; 214 delete profileDataGridNode._searchMatchedTotalColumn; 215 delete profileDataGridNode._searchMatchedFunctionColumn; 216 217 if (percentUnits) { 218 if (lessThan) { 219 if (profileDataGridNode.selfPercent < queryNumber) 220 profileDataGridNode._searchMatchedSelfColumn = true; 221 if (profileDataGridNode.totalPercent < queryNumber) 222 profileDataGridNode._searchMatchedTotalColumn = true; 223 } else if (greaterThan) { 224 if (profileDataGridNode.selfPercent > queryNumber) 225 profileDataGridNode._searchMatchedSelfColumn = true; 226 if (profileDataGridNode.totalPercent > queryNumber) 227 profileDataGridNode._searchMatchedTotalColumn = true; 228 } 229 230 if (equalTo) { 231 if (profileDataGridNode.selfPercent == queryNumber) 232 profileDataGridNode._searchMatchedSelfColumn = true; 233 if (profileDataGridNode.totalPercent == queryNumber) 234 profileDataGridNode._searchMatchedTotalColumn = true; 235 } 236 } else if (millisecondsUnits || secondsUnits) { 237 if (lessThan) { 238 if (profileDataGridNode.selfTime < queryNumberMilliseconds) 239 profileDataGridNode._searchMatchedSelfColumn = true; 240 if (profileDataGridNode.totalTime < queryNumberMilliseconds) 241 profileDataGridNode._searchMatchedTotalColumn = true; 242 } else if (greaterThan) { 243 if (profileDataGridNode.selfTime > queryNumberMilliseconds) 244 profileDataGridNode._searchMatchedSelfColumn = true; 245 if (profileDataGridNode.totalTime > queryNumberMilliseconds) 246 profileDataGridNode._searchMatchedTotalColumn = true; 247 } 248 249 if (equalTo) { 250 if (profileDataGridNode.selfTime == queryNumberMilliseconds) 251 profileDataGridNode._searchMatchedSelfColumn = true; 252 if (profileDataGridNode.totalTime == queryNumberMilliseconds) 253 profileDataGridNode._searchMatchedTotalColumn = true; 254 } 255 } 256 257 if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher))) 258 profileDataGridNode._searchMatchedFunctionColumn = true; 259 260 if (profileDataGridNode._searchMatchedSelfColumn || 261 profileDataGridNode._searchMatchedTotalColumn || 262 profileDataGridNode._searchMatchedFunctionColumn) 263 { 264 profileDataGridNode.refresh(); 265 return true; 266 } 267 268 return false; 269 } 270 271 var current = this.profileDataGridTree.children[0]; 272 273 while (current) { 274 if (matchesQuery(current)) { 275 this._searchResults.push({ profileNode: current }); 276 } 277 278 current = current.traverseNextNode(false, null, false); 279 } 280 281 finishedCallback(this, this._searchResults.length); 282 }, 283 284 jumpToFirstSearchResult: function() 285 { 286 if (!this._searchResults || !this._searchResults.length) 287 return; 288 this._currentSearchResultIndex = 0; 289 this._jumpToSearchResult(this._currentSearchResultIndex); 290 }, 291 292 jumpToLastSearchResult: function() 293 { 294 if (!this._searchResults || !this._searchResults.length) 295 return; 296 this._currentSearchResultIndex = (this._searchResults.length - 1); 297 this._jumpToSearchResult(this._currentSearchResultIndex); 298 }, 299 300 jumpToNextSearchResult: function() 301 { 302 if (!this._searchResults || !this._searchResults.length) 303 return; 304 if (++this._currentSearchResultIndex >= this._searchResults.length) 305 this._currentSearchResultIndex = 0; 306 this._jumpToSearchResult(this._currentSearchResultIndex); 307 }, 308 309 jumpToPreviousSearchResult: function() 310 { 311 if (!this._searchResults || !this._searchResults.length) 312 return; 313 if (--this._currentSearchResultIndex < 0) 314 this._currentSearchResultIndex = (this._searchResults.length - 1); 315 this._jumpToSearchResult(this._currentSearchResultIndex); 316 }, 317 318 /** 319 * @return {boolean} 320 */ 321 showingFirstSearchResult: function() 322 { 323 return (this._currentSearchResultIndex === 0); 324 }, 325 326 /** 327 * @return {boolean} 328 */ 329 showingLastSearchResult: function() 330 { 331 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); 332 }, 333 334 /** 335 * @return {number} 336 */ 337 currentSearchResultIndex: function() { 338 return this._currentSearchResultIndex; 339 }, 340 341 _jumpToSearchResult: function(index) 342 { 343 var searchResult = this._searchResults[index]; 344 if (!searchResult) 345 return; 346 347 var profileNode = searchResult.profileNode; 348 profileNode.revealAndSelect(); 349 }, 350 351 _ensureFlameChartCreated: function() 352 { 353 if (this._flameChart) 354 return; 355 this._dataProvider = new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target()); 356 this._flameChart = new WebInspector.CPUProfileFlameChart(this._dataProvider); 357 this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this)); 358 }, 359 360 /** 361 * @param {!WebInspector.Event} event 362 */ 363 _onEntrySelected: function(event) 364 { 365 var entryIndex = event.data; 366 var node = this._dataProvider._entryNodes[entryIndex]; 367 if (!node || !node.scriptId) 368 return; 369 var script = WebInspector.debuggerModel.scriptForId(node.scriptId) 370 if (!script) 371 return; 372 WebInspector.Revealer.reveal(script.rawLocationToUILocation(node.lineNumber)); 373 }, 374 375 _changeView: function() 376 { 377 if (!this.profile) 378 return; 379 380 switch (this.viewSelectComboBox.selectedOption().value) { 381 case WebInspector.CPUProfileView._TypeFlame: 382 this._ensureFlameChartCreated(); 383 this.dataGrid.detach(); 384 this._flameChart.show(this.element); 385 this._viewType.set(WebInspector.CPUProfileView._TypeFlame); 386 this._statusBarButtonsElement.classList.toggle("hidden", true); 387 return; 388 case WebInspector.CPUProfileView._TypeTree: 389 this.profileDataGridTree = this._getTopDownProfileDataGridTree(); 390 this._sortProfile(); 391 this._viewType.set(WebInspector.CPUProfileView._TypeTree); 392 break; 393 case WebInspector.CPUProfileView._TypeHeavy: 394 this.profileDataGridTree = this._getBottomUpProfileDataGridTree(); 395 this._sortProfile(); 396 this._viewType.set(WebInspector.CPUProfileView._TypeHeavy); 397 break; 398 } 399 400 this._statusBarButtonsElement.classList.toggle("hidden", false); 401 402 if (this._flameChart) 403 this._flameChart.detach(); 404 this.dataGrid.show(this.element); 405 406 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 407 return; 408 409 // The current search needs to be performed again. First negate out previous match 410 // count by calling the search finished callback with a negative number of matches. 411 // Then perform the search again the with same query and callback. 412 this._searchFinishedCallback(this, -this._searchResults.length); 413 this.performSearch(this.currentQuery, this._searchFinishedCallback); 414 }, 415 416 _focusClicked: function(event) 417 { 418 if (!this.dataGrid.selectedNode) 419 return; 420 421 this.resetButton.visible = true; 422 this.profileDataGridTree.focus(this.dataGrid.selectedNode); 423 this.refresh(); 424 this.refreshVisibleData(); 425 }, 426 427 _excludeClicked: function(event) 428 { 429 var selectedNode = this.dataGrid.selectedNode 430 431 if (!selectedNode) 432 return; 433 434 selectedNode.deselect(); 435 436 this.resetButton.visible = true; 437 this.profileDataGridTree.exclude(selectedNode); 438 this.refresh(); 439 this.refreshVisibleData(); 440 }, 441 442 _resetClicked: function(event) 443 { 444 this.resetButton.visible = false; 445 this.profileDataGridTree.restore(); 446 this._linkifier.reset(); 447 this.refresh(); 448 this.refreshVisibleData(); 449 }, 450 451 _dataGridNodeSelected: function(node) 452 { 453 this.focusButton.setEnabled(true); 454 this.excludeButton.setEnabled(true); 455 }, 456 457 _dataGridNodeDeselected: function(node) 458 { 459 this.focusButton.setEnabled(false); 460 this.excludeButton.setEnabled(false); 461 }, 462 463 _sortProfile: function() 464 { 465 var sortAscending = this.dataGrid.isSortOrderAscending(); 466 var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier(); 467 var sortProperty = { 468 "self": "selfTime", 469 "total": "totalTime", 470 "function": "functionName" 471 }[sortColumnIdentifier]; 472 473 this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); 474 475 this.refresh(); 476 }, 477 478 __proto__: WebInspector.VBox.prototype 479} 480 481/** 482 * @constructor 483 * @extends {WebInspector.ProfileType} 484 * @implements {WebInspector.CPUProfilerModel.Delegate} 485 */ 486WebInspector.CPUProfileType = function() 487{ 488 WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile")); 489 this._recording = false; 490 491 this._nextAnonymousConsoleProfileNumber = 1; 492 this._anonymousConsoleProfileIdToTitle = {}; 493 494 WebInspector.CPUProfileType.instance = this; 495 WebInspector.cpuProfilerModel.setDelegate(this); 496} 497 498WebInspector.CPUProfileType.TypeId = "CPU"; 499 500WebInspector.CPUProfileType.prototype = { 501 /** 502 * @override 503 * @return {string} 504 */ 505 fileExtension: function() 506 { 507 return ".cpuprofile"; 508 }, 509 510 get buttonTooltip() 511 { 512 return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling."); 513 }, 514 515 /** 516 * @override 517 * @return {boolean} 518 */ 519 buttonClicked: function() 520 { 521 if (this._recording) { 522 this.stopRecordingProfile(); 523 return false; 524 } else { 525 this.startRecordingProfile(); 526 return true; 527 } 528 }, 529 530 get treeItemTitle() 531 { 532 return WebInspector.UIString("CPU PROFILES"); 533 }, 534 535 get description() 536 { 537 return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions."); 538 }, 539 540 /** 541 * @param {string} id 542 * @param {!WebInspector.DebuggerModel.Location} scriptLocation 543 * @param {string=} title 544 */ 545 consoleProfileStarted: function(id, scriptLocation, title) 546 { 547 var resolvedTitle = title; 548 if (!resolvedTitle) { 549 resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++); 550 this._anonymousConsoleProfileIdToTitle[id] = resolvedTitle; 551 } 552 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle)); 553 }, 554 555 /** 556 * @param {string} protocolId 557 * @param {!WebInspector.DebuggerModel.Location} scriptLocation 558 * @param {!ProfilerAgent.CPUProfile} cpuProfile 559 * @param {string=} title 560 */ 561 consoleProfileFinished: function(protocolId, scriptLocation, cpuProfile, title) 562 { 563 var resolvedTitle = title; 564 if (typeof title === "undefined") { 565 resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId]; 566 delete this._anonymousConsoleProfileIdToTitle[protocolId]; 567 } 568 569 var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); 570 var profile = new WebInspector.CPUProfileHeader(target, this, resolvedTitle); 571 profile.setProtocolProfile(cpuProfile); 572 this.addProfile(profile); 573 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle)); 574 }, 575 576 /** 577 * @param {string} type 578 * @param {!WebInspector.DebuggerModel.Location} scriptLocation 579 * @param {string} messageText 580 */ 581 _addMessageToConsole: function(type, scriptLocation, messageText) 582 { 583 var script = scriptLocation.script(); 584 var message = new WebInspector.ConsoleMessage( 585 WebInspector.console.target(), 586 WebInspector.ConsoleMessage.MessageSource.ConsoleAPI, 587 WebInspector.ConsoleMessage.MessageLevel.Debug, 588 messageText, 589 type, 590 undefined, 591 undefined, 592 undefined, 593 undefined, 594 undefined, 595 [{ 596 functionName: "", 597 scriptId: scriptLocation.scriptId, 598 url: script ? script.contentURL() : "", 599 lineNumber: scriptLocation.lineNumber, 600 columnNumber: scriptLocation.columnNumber || 0 601 }]); 602 603 WebInspector.console.addMessage(message); 604 }, 605 606 /** 607 * @return {boolean} 608 */ 609 isRecordingProfile: function() 610 { 611 return this._recording; 612 }, 613 614 startRecordingProfile: function() 615 { 616 if (this._profileBeingRecorded) 617 return; 618 var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); 619 var profile = new WebInspector.CPUProfileHeader(target, this); 620 this.setProfileBeingRecorded(profile); 621 this.addProfile(profile); 622 profile.updateStatus(WebInspector.UIString("Recording\u2026")); 623 this._recording = true; 624 WebInspector.cpuProfilerModel.setRecording(true); 625 WebInspector.userMetrics.ProfilesCPUProfileTaken.record(); 626 ProfilerAgent.start(); 627 }, 628 629 stopRecordingProfile: function() 630 { 631 this._recording = false; 632 WebInspector.cpuProfilerModel.setRecording(false); 633 634 /** 635 * @param {?string} error 636 * @param {?ProfilerAgent.CPUProfile} profile 637 * @this {WebInspector.CPUProfileType} 638 */ 639 function didStopProfiling(error, profile) 640 { 641 if (!this._profileBeingRecorded) 642 return; 643 this._profileBeingRecorded.setProtocolProfile(profile); 644 this._profileBeingRecorded.updateStatus(""); 645 var recordedProfile = this._profileBeingRecorded; 646 this.setProfileBeingRecorded(null); 647 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile); 648 } 649 ProfilerAgent.stop(didStopProfiling.bind(this)); 650 }, 651 652 /** 653 * @override 654 * @param {string} title 655 * @return {!WebInspector.ProfileHeader} 656 */ 657 createProfileLoadedFromFile: function(title) 658 { 659 var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); 660 return new WebInspector.CPUProfileHeader(target, 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.messageSink.addErrorMessage("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", 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