2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 * @extends {WebInspector.View}
29 * @param {!WebInspector.CPUProfileHeader} profileHeader
31 WebInspector.CPUProfileView = function(profileHeader)
33 WebInspector.View.call(this);
35 this.element.classList.add("profile-view");
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);
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});
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);
52 this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
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);
59 var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame;
60 var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame];
61 this.viewSelectComboBox.select(option);
63 this._statusBarButtonsElement = document.createElement("span");
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);
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);
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);
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);
84 this.profileHead = /** @type {?ProfilerAgent.CPUProfileNode} */ (null);
85 this.profile = profileHeader;
87 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
89 if (this.profile._profile) // If the profile has been loaded from file then use it.
90 this._processProfileData(this.profile._profile);
92 this._processProfileData(this.profile.protocolProfile());
95 WebInspector.CPUProfileView._TypeFlame = "Flame";
96 WebInspector.CPUProfileView._TypeTree = "Tree";
97 WebInspector.CPUProfileView._TypeHeavy = "Heavy";
99 WebInspector.CPUProfileView.prototype = {
101 * @param {!number} timeLeft
102 * @param {!number} timeRight
104 selectRange: function(timeLeft, timeRight)
106 if (!this._flameChart)
108 this._flameChart.selectRange(timeLeft, timeRight);
111 _revealProfilerNode: function(event)
113 var current = this.profileDataGridTree.children[0];
115 while (current && current.profileNode !== event.data)
116 current = current.traverseNextNode(false, null, false);
119 current.revealAndSelect();
123 * @param {?ProfilerAgent.CPUProfile} profile
125 _processProfileData: function(profile)
127 this.profileHead = profile.head;
128 this.samples = profile.samples;
130 this._calculateTimes(profile);
132 this._assignParentsInProfile();
134 this._buildIdToNodeMap();
136 this._updatePercentButton();
137 if (this._flameChart)
138 this._flameChart.update();
143 return [this.viewSelectComboBox.element, this._statusBarButtonsElement];
147 * @return {!WebInspector.ProfileDataGridTree}
149 _getBottomUpProfileDataGridTree: function()
151 if (!this._bottomUpProfileDataGridTree)
152 this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profileHead));
153 return this._bottomUpProfileDataGridTree;
157 * @return {!WebInspector.ProfileDataGridTree}
159 _getTopDownProfileDataGridTree: function()
161 if (!this._topDownProfileDataGridTree)
162 this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profileHead));
163 return this._topDownProfileDataGridTree;
168 this._currentSearchResultIndex = -1;
173 var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
175 this.dataGrid.rootNode().removeChildren();
177 var children = this.profileDataGridTree.children;
178 var count = children.length;
180 for (var index = 0; index < count; ++index)
181 this.dataGrid.rootNode().appendChild(children[index]);
183 if (selectedProfileNode)
184 selectedProfileNode.selected = true;
187 refreshVisibleData: function()
189 var child = this.dataGrid.rootNode().children[0];
192 child = child.traverseNextNode(false, null, true);
196 refreshShowAsPercents: function()
198 this._updatePercentButton();
199 this.refreshVisibleData();
202 searchCanceled: function()
204 if (this._searchResults) {
205 for (var i = 0; i < this._searchResults.length; ++i) {
206 var profileNode = this._searchResults[i].profileNode;
208 delete profileNode._searchMatchedSelfColumn;
209 delete profileNode._searchMatchedTotalColumn;
210 delete profileNode._searchMatchedFunctionColumn;
212 profileNode.refresh();
216 delete this._searchFinishedCallback;
217 this._currentSearchResultIndex = -1;
218 this._searchResults = [];
221 performSearch: function(query, finishedCallback)
223 // Call searchCanceled since it will reset everything we need before doing a new search.
224 this.searchCanceled();
226 query = query.trim();
231 this._searchFinishedCallback = finishedCallback;
233 var greaterThan = (query.startsWith(">"));
234 var lessThan = (query.startsWith("<"));
235 var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
236 var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
237 var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
238 var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
240 var queryNumber = parseFloat(query);
241 if (greaterThan || lessThan || equalTo) {
242 if (equalTo && (greaterThan || lessThan))
243 queryNumber = parseFloat(query.substring(2));
245 queryNumber = parseFloat(query.substring(1));
248 var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
250 // Make equalTo implicitly true if it wasn't specified there is no other operator.
251 if (!isNaN(queryNumber) && !(greaterThan || lessThan))
254 var matcher = createPlainTextSearchRegex(query, "i");
256 function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
258 delete profileDataGridNode._searchMatchedSelfColumn;
259 delete profileDataGridNode._searchMatchedTotalColumn;
260 delete profileDataGridNode._searchMatchedFunctionColumn;
264 if (profileDataGridNode.selfPercent < queryNumber)
265 profileDataGridNode._searchMatchedSelfColumn = true;
266 if (profileDataGridNode.totalPercent < queryNumber)
267 profileDataGridNode._searchMatchedTotalColumn = true;
268 } else if (greaterThan) {
269 if (profileDataGridNode.selfPercent > queryNumber)
270 profileDataGridNode._searchMatchedSelfColumn = true;
271 if (profileDataGridNode.totalPercent > queryNumber)
272 profileDataGridNode._searchMatchedTotalColumn = true;
276 if (profileDataGridNode.selfPercent == queryNumber)
277 profileDataGridNode._searchMatchedSelfColumn = true;
278 if (profileDataGridNode.totalPercent == queryNumber)
279 profileDataGridNode._searchMatchedTotalColumn = true;
281 } else if (millisecondsUnits || secondsUnits) {
283 if (profileDataGridNode.selfTime < queryNumberMilliseconds)
284 profileDataGridNode._searchMatchedSelfColumn = true;
285 if (profileDataGridNode.totalTime < queryNumberMilliseconds)
286 profileDataGridNode._searchMatchedTotalColumn = true;
287 } else if (greaterThan) {
288 if (profileDataGridNode.selfTime > queryNumberMilliseconds)
289 profileDataGridNode._searchMatchedSelfColumn = true;
290 if (profileDataGridNode.totalTime > queryNumberMilliseconds)
291 profileDataGridNode._searchMatchedTotalColumn = true;
295 if (profileDataGridNode.selfTime == queryNumberMilliseconds)
296 profileDataGridNode._searchMatchedSelfColumn = true;
297 if (profileDataGridNode.totalTime == queryNumberMilliseconds)
298 profileDataGridNode._searchMatchedTotalColumn = true;
302 if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
303 profileDataGridNode._searchMatchedFunctionColumn = true;
305 if (profileDataGridNode._searchMatchedSelfColumn ||
306 profileDataGridNode._searchMatchedTotalColumn ||
307 profileDataGridNode._searchMatchedFunctionColumn)
309 profileDataGridNode.refresh();
316 var current = this.profileDataGridTree.children[0];
319 if (matchesQuery(current)) {
320 this._searchResults.push({ profileNode: current });
323 current = current.traverseNextNode(false, null, false);
326 finishedCallback(this, this._searchResults.length);
329 jumpToFirstSearchResult: function()
331 if (!this._searchResults || !this._searchResults.length)
333 this._currentSearchResultIndex = 0;
334 this._jumpToSearchResult(this._currentSearchResultIndex);
337 jumpToLastSearchResult: function()
339 if (!this._searchResults || !this._searchResults.length)
341 this._currentSearchResultIndex = (this._searchResults.length - 1);
342 this._jumpToSearchResult(this._currentSearchResultIndex);
345 jumpToNextSearchResult: function()
347 if (!this._searchResults || !this._searchResults.length)
349 if (++this._currentSearchResultIndex >= this._searchResults.length)
350 this._currentSearchResultIndex = 0;
351 this._jumpToSearchResult(this._currentSearchResultIndex);
354 jumpToPreviousSearchResult: function()
356 if (!this._searchResults || !this._searchResults.length)
358 if (--this._currentSearchResultIndex < 0)
359 this._currentSearchResultIndex = (this._searchResults.length - 1);
360 this._jumpToSearchResult(this._currentSearchResultIndex);
366 showingFirstSearchResult: function()
368 return (this._currentSearchResultIndex === 0);
374 showingLastSearchResult: function()
376 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
382 currentSearchResultIndex: function() {
383 return this._currentSearchResultIndex;
386 _jumpToSearchResult: function(index)
388 var searchResult = this._searchResults[index];
392 var profileNode = searchResult.profileNode;
393 profileNode.revealAndSelect();
396 _ensureFlameChartCreated: function()
398 if (this._flameChart)
400 var dataProvider = new WebInspector.CPUFlameChartDataProvider(this);
401 this._flameChart = new WebInspector.FlameChart(dataProvider);
402 this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this));
406 * @param {!WebInspector.Event} event
408 _onEntrySelected: function(event)
410 var node = event.data;
411 if (!node || !node.scriptId)
413 var script = WebInspector.debuggerModel.scriptForId(node.scriptId)
416 var uiLocation = script.rawLocationToUILocation(node.lineNumber);
421 _changeView: function()
426 switch (this.viewSelectComboBox.selectedOption().value) {
427 case WebInspector.CPUProfileView._TypeFlame:
428 this._ensureFlameChartCreated();
429 this.dataGrid.detach();
430 this._flameChart.show(this.element);
431 this._viewType.set(WebInspector.CPUProfileView._TypeFlame);
432 this._statusBarButtonsElement.enableStyleClass("hidden", true);
434 case WebInspector.CPUProfileView._TypeTree:
435 this.profileDataGridTree = this._getTopDownProfileDataGridTree();
437 this._viewType.set(WebInspector.CPUProfileView._TypeTree);
439 case WebInspector.CPUProfileView._TypeHeavy:
440 this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
442 this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
446 this._statusBarButtonsElement.enableStyleClass("hidden", false);
448 if (this._flameChart)
449 this._flameChart.detach();
450 this.dataGrid.show(this.element);
452 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
455 // The current search needs to be performed again. First negate out previous match
456 // count by calling the search finished callback with a negative number of matches.
457 // Then perform the search again the with same query and callback.
458 this._searchFinishedCallback(this, -this._searchResults.length);
459 this.performSearch(this.currentQuery, this._searchFinishedCallback);
462 _percentClicked: function(event)
464 var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get();
465 this.showSelfTimeAsPercent.set(!currentState);
466 this.showTotalTimeAsPercent.set(!currentState);
467 this.showAverageTimeAsPercent.set(!currentState);
468 this.refreshShowAsPercents();
471 _updatePercentButton: function()
473 if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) {
474 this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
475 this.percentButton.toggled = true;
477 this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
478 this.percentButton.toggled = false;
482 _focusClicked: function(event)
484 if (!this.dataGrid.selectedNode)
487 this.resetButton.visible = true;
488 this.profileDataGridTree.focus(this.dataGrid.selectedNode);
490 this.refreshVisibleData();
493 _excludeClicked: function(event)
495 var selectedNode = this.dataGrid.selectedNode
500 selectedNode.deselect();
502 this.resetButton.visible = true;
503 this.profileDataGridTree.exclude(selectedNode);
505 this.refreshVisibleData();
508 _resetClicked: function(event)
510 this.resetButton.visible = false;
511 this.profileDataGridTree.restore();
512 this._linkifier.reset();
514 this.refreshVisibleData();
517 _dataGridNodeSelected: function(node)
519 this.focusButton.setEnabled(true);
520 this.excludeButton.setEnabled(true);
523 _dataGridNodeDeselected: function(node)
525 this.focusButton.setEnabled(false);
526 this.excludeButton.setEnabled(false);
529 _sortProfile: function()
531 var sortAscending = this.dataGrid.isSortOrderAscending();
532 var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
535 "total": "totalTime",
536 "function": "functionName"
537 }[sortColumnIdentifier];
539 this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
544 _mouseDownInDataGrid: function(event)
546 if (event.detail < 2)
549 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
550 if (!cell || (!cell.classList.contains("total-column") && !cell.classList.contains("self-column") && !cell.classList.contains("average-column")))
553 if (cell.classList.contains("total-column"))
554 this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get());
555 else if (cell.classList.contains("self-column"))
556 this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get());
557 else if (cell.classList.contains("average-column"))
558 this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get());
560 this.refreshShowAsPercents();
565 _calculateTimes: function(profile)
567 function totalHitCount(node) {
568 var result = node.hitCount;
569 for (var i = 0; i < node.children.length; i++)
570 result += totalHitCount(node.children[i]);
573 profile.totalHitCount = totalHitCount(profile.head);
575 var durationMs = 1000 * (profile.endTime - profile.startTime);
576 var samplingInterval = durationMs / profile.totalHitCount;
577 this.samplingIntervalMs = samplingInterval;
579 function calculateTimesForNode(node) {
580 node.selfTime = node.hitCount * samplingInterval;
581 var totalHitCount = node.hitCount;
582 for (var i = 0; i < node.children.length; i++)
583 totalHitCount += calculateTimesForNode(node.children[i]);
584 node.totalTime = totalHitCount * samplingInterval;
585 return totalHitCount;
587 calculateTimesForNode(profile.head);
590 _assignParentsInProfile: function()
592 var head = this.profileHead;
595 var nodesToTraverse = [ head ];
596 while (nodesToTraverse.length) {
597 var parent = nodesToTraverse.pop();
598 var children = parent.children;
599 var length = children.length;
600 for (var i = 0; i < length; ++i) {
601 var child = children[i];
603 child.parent = parent;
604 if (child.children.length)
605 nodesToTraverse.push(child);
610 _buildIdToNodeMap: function()
612 var idToNode = this._idToNode = {};
613 var stack = [this.profileHead];
614 while (stack.length) {
615 var node = stack.pop();
616 idToNode[node.id] = node;
617 for (var i = 0; i < node.children.length; i++)
618 stack.push(node.children[i]);
621 var topLevelNodes = this.profileHead.children;
622 for (var i = 0; i < topLevelNodes.length; i++) {
623 var node = topLevelNodes[i];
624 if (node.functionName === "(garbage collector)") {
631 __proto__: WebInspector.View.prototype
636 * @extends {WebInspector.ProfileType}
637 * @implements {WebInspector.CPUProfilerModel.Delegate}
639 WebInspector.CPUProfileType = function()
641 WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
642 this._recording = false;
643 this._nextProfileId = 1;
645 this._nextAnonymousConsoleProfileNumber = 1;
646 this._anonymousConsoleProfileIdToTitle = {};
648 WebInspector.CPUProfileType.instance = this;
649 WebInspector.cpuProfilerModel.setDelegate(this);
652 WebInspector.CPUProfileType.TypeId = "CPU";
654 WebInspector.CPUProfileType.prototype = {
659 fileExtension: function()
661 return ".cpuprofile";
666 return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
673 buttonClicked: function()
675 if (this._recording) {
676 this.stopRecordingProfile();
679 this.startRecordingProfile();
686 return WebInspector.UIString("CPU PROFILES");
691 return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
696 * @param {!DebuggerAgent.Location} scriptLocation
697 * @param {string=} title
699 consoleProfileStarted: function(id, scriptLocation, title)
701 var resolvedTitle = title;
702 if (!resolvedTitle) {
703 resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++);
704 this._anonymousConsoleProfileIdToTitle[id] = resolvedTitle;
706 var messageElement = document.createTextNode(WebInspector.UIString("Profile '%s' started.", resolvedTitle));
707 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, messageElement);
711 * @param {string} protocolId
712 * @param {!DebuggerAgent.Location} scriptLocation
713 * @param {!ProfilerAgent.CPUProfile} cpuProfile
714 * @param {string=} title
716 consoleProfileFinished: function(protocolId, scriptLocation, cpuProfile, title)
718 var resolvedTitle = title;
719 if (typeof title === "undefined") {
720 resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId];
721 delete this._anonymousConsoleProfileIdToTitle[protocolId];
724 var id = this._nextProfileId++;
725 var profile = new WebInspector.CPUProfileHeader(this, resolvedTitle, id);
726 profile.setProtocolProfile(cpuProfile);
727 this.addProfile(profile);
729 var messageElement = document.createElement("span");
730 messageElement.createTextChild("Profile '");
731 var a = messageElement.createChild("span", "link");
732 a.title = resolvedTitle;
733 a.textContent = resolvedTitle;
734 a.addEventListener("click", onClick.bind(this), true);
735 function onClick(event)
737 WebInspector.showPanel("profiles").showProfile(WebInspector.CPUProfileType.TypeId, id);
739 messageElement.createTextChild("' finished.");
741 this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, messageElement);
745 * @param {string} type
746 * @param {!DebuggerAgent.Location} scriptLocation
747 * @param {!Node} messageElement
749 _addMessageToConsole: function(type, scriptLocation, messageElement)
751 var rawLocation = new WebInspector.DebuggerModel.Location(scriptLocation.scriptId, scriptLocation.lineNumber, scriptLocation.columnNumber || 0);
752 var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(rawLocation);
755 url = uiLocation.url();
756 var message = WebInspector.ConsoleMessage.create(
757 WebInspector.ConsoleMessage.MessageSource.ConsoleAPI,
758 WebInspector.ConsoleMessage.MessageLevel.Debug,
762 scriptLocation.lineNumber,
763 scriptLocation.columnNumber);
765 message.setMessageElement(messageElement);
766 WebInspector.console.addMessage(message);
772 isRecordingProfile: function()
774 return this._recording;
777 startRecordingProfile: function()
779 if (this._profileBeingRecorded)
781 var id = this._nextProfileId++;
782 this._profileBeingRecorded = new WebInspector.CPUProfileHeader(this, WebInspector.UIString("Recording\u2026"), id);
783 this.addProfile(this._profileBeingRecorded);
785 this._recording = true;
786 WebInspector.cpuProfilerModel.setRecording(true);
787 WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
788 ProfilerAgent.start();
791 stopRecordingProfile: function()
793 this._recording = false;
794 WebInspector.cpuProfilerModel.setRecording(false);
797 * @param {?string} error
798 * @param {?ProfilerAgent.CPUProfile} profile
799 * @this {WebInspector.CPUProfileType}
801 function didStopProfiling(error, profile)
803 if (!this._profileBeingRecorded)
805 this._profileBeingRecorded.setProtocolProfile(profile);
807 var title = WebInspector.UIString("Profile %d", this._profileBeingRecorded.uid);
808 this._profileBeingRecorded.title = title;
809 this._profileBeingRecorded.sidebarElement.mainTitle = title;
810 var recordedProfile = this._profileBeingRecorded;
811 this._profileBeingRecorded = null;
812 WebInspector.panels.profiles._showProfile(recordedProfile);
814 ProfilerAgent.stop(didStopProfiling.bind(this));
819 * @param {string} title
820 * @return {!WebInspector.ProfileHeader}
822 createProfileLoadedFromFile: function(title)
824 return new WebInspector.CPUProfileHeader(this, title);
830 removeProfile: function(profile)
832 if (this._profileBeingRecorded === profile)
833 this.stopRecordingProfile();
834 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
837 __proto__: WebInspector.ProfileType.prototype
842 * @extends {WebInspector.ProfileHeader}
843 * @implements {WebInspector.OutputStream}
844 * @implements {WebInspector.OutputStreamDelegate}
845 * @param {!WebInspector.CPUProfileType} type
846 * @param {string} title
847 * @param {number=} uid
849 WebInspector.CPUProfileHeader = function(type, title, uid)
851 WebInspector.ProfileHeader.call(this, type, title, uid);
852 this._tempFile = null;
855 WebInspector.CPUProfileHeader.prototype = {
856 onTransferStarted: function()
858 this._jsonifiedProfile = "";
859 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length));
863 * @param {!WebInspector.ChunkedReader} reader
865 onChunkTransferred: function(reader)
867 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length));
870 onTransferFinished: function()
872 this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
873 this._profile = JSON.parse(this._jsonifiedProfile);
874 this._jsonifiedProfile = null;
875 this.sidebarElement.subtitle = WebInspector.UIString("Loaded");
877 if (this._profileType._profileBeingRecorded === this)
878 this._profileType._profileBeingRecorded = null;
882 * @param {!WebInspector.ChunkedReader} reader
884 onError: function(reader, e)
886 switch(e.target.error.code) {
887 case e.target.error.NOT_FOUND_ERR:
888 this.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
890 case e.target.error.NOT_READABLE_ERR:
891 this.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
893 case e.target.error.ABORT_ERR:
896 this.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
901 * @param {string} text
903 write: function(text)
905 this._jsonifiedProfile += text;
908 close: function() { },
915 this.removeTempFile();
920 * @return {!WebInspector.ProfileSidebarTreeElement}
922 createSidebarTreeElement: function()
924 return new WebInspector.ProfileSidebarTreeElement(this, "profile-sidebar-tree-item");
929 * @param {!WebInspector.ProfilesPanel} profilesPanel
930 * @return {!WebInspector.CPUProfileView}
932 createView: function(profilesPanel)
934 return new WebInspector.CPUProfileView(this);
941 canSaveToFile: function()
943 return !!this._tempFile;
946 saveToFile: function()
948 var fileOutputStream = new WebInspector.FileOutputStream();
951 * @param {boolean} accepted
952 * @this {WebInspector.CPUProfileHeader}
954 function onOpenForSave(accepted)
958 function didRead(data)
961 fileOutputStream.write(data, fileOutputStream.close.bind(fileOutputStream));
963 fileOutputStream.close();
965 this._tempFile.read(didRead.bind(this));
967 this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
968 fileOutputStream.open(this._fileName, onOpenForSave.bind(this));
972 * @param {!File} file
974 loadFromFile: function(file)
976 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
977 this.sidebarElement.wait = true;
979 var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this);
980 fileReader.start(this);
985 * @return {?ProfilerAgent.CPUProfile}
987 protocolProfile: function()
989 return this._protocolProfile;
993 * @param {!ProfilerAgent.CPUProfile} cpuProfile
995 setProtocolProfile: function(cpuProfile)
997 this._protocolProfile = cpuProfile;
998 this._saveProfileDataToTempFile(cpuProfile);
1002 * @param {!ProfilerAgent.CPUProfile} data
1004 _saveProfileDataToTempFile: function(data)
1006 var serializedData = JSON.stringify(data);
1009 * @this {WebInspector.CPUProfileHeader}
1011 function didCreateTempFile(tempFile)
1013 this._writeToTempFile(tempFile, serializedData);
1015 new WebInspector.TempFile("cpu-profiler", this.uid, didCreateTempFile.bind(this));
1019 * @param {?WebInspector.TempFile} tempFile
1020 * @param {string} serializedData
1022 _writeToTempFile: function(tempFile, serializedData)
1024 this._tempFile = tempFile;
1026 tempFile.write(serializedData, tempFile.finishWriting.bind(tempFile));
1029 __proto__: WebInspector.ProfileHeader.prototype
1034 * @implements {WebInspector.FlameChartDataProvider}
1036 WebInspector.CPUFlameChartDataProvider = function(cpuProfileView)
1038 WebInspector.FlameChartDataProvider.call(this);
1039 this._cpuProfileView = cpuProfileView;
1042 WebInspector.CPUFlameChartDataProvider.prototype = {
1044 * @param {!WebInspector.FlameChart.ColorGenerator} colorGenerator
1047 timelineData: function(colorGenerator)
1049 return this._timelineData || this._calculateTimelineData(colorGenerator);
1053 * @param {!WebInspector.FlameChart.ColorGenerator} colorGenerator
1056 _calculateTimelineData: function(colorGenerator)
1058 if (!this._cpuProfileView.profileHead)
1061 var samples = this._cpuProfileView.samples;
1062 var idToNode = this._cpuProfileView._idToNode;
1063 var gcNode = this._cpuProfileView._gcNode;
1064 var samplesCount = samples.length;
1065 var samplingInterval = this._cpuProfileView.samplingIntervalMs;
1069 var openIntervals = [];
1070 var stackTrace = [];
1071 var colorEntryIndexes = [];
1072 var maxDepth = 5; // minimum stack depth for the case when we see no activity.
1077 * @param {!Object} colorPair
1078 * @param {!number} depth
1079 * @param {!number} duration
1080 * @param {!number} startTime
1081 * @param {!Object} node
1083 function ChartEntry(colorPair, depth, duration, startTime, node)
1085 this.colorPair = colorPair;
1087 this.duration = duration;
1088 this.startTime = startTime;
1092 var entries = /** @type {!Array.<!ChartEntry>} */ ([]);
1094 for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
1095 var node = idToNode[samples[sampleIndex]];
1096 stackTrace.length = 0;
1098 stackTrace.push(node);
1101 stackTrace.pop(); // Remove (root) node
1103 maxDepth = Math.max(maxDepth, depth);
1105 node = stackTrace.pop();
1108 // GC samples have no stack, so we just put GC node on top of the last recoreded sample.
1109 if (node === gcNode) {
1110 while (depth < openIntervals.length) {
1111 intervalIndex = openIntervals[depth].index;
1112 entries[intervalIndex].duration += samplingInterval;
1115 // If previous stack is also GC then just continue.
1116 if (openIntervals.length > 0 && openIntervals.peekLast().node === node) {
1117 entries[intervalIndex].selfTime += samplingInterval;
1122 while (node && depth < openIntervals.length && node === openIntervals[depth].node) {
1123 intervalIndex = openIntervals[depth].index;
1124 entries[intervalIndex].duration += samplingInterval;
1125 node = stackTrace.pop();
1128 if (depth < openIntervals.length)
1129 openIntervals.length = depth;
1131 entries[intervalIndex].selfTime += samplingInterval;
1136 var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
1137 var indexesForColor = colorEntryIndexes[colorPair.index];
1138 if (!indexesForColor)
1139 indexesForColor = colorEntryIndexes[colorPair.index] = [];
1141 var entry = new ChartEntry(colorPair, depth, samplingInterval, sampleIndex * samplingInterval, node);
1142 indexesForColor.push(entries.length);
1143 entries.push(entry);
1144 openIntervals.push({node: node, index: index});
1147 node = stackTrace.pop();
1150 entries[entries.length - 1].selfTime += samplingInterval;
1153 var entryNodes = new Array(entries.length);
1154 var entryColorIndexes = new Uint16Array(entries.length);
1155 var entryLevels = new Uint8Array(entries.length);
1156 var entryTotalTimes = new Float32Array(entries.length);
1157 var entrySelfTimes = new Float32Array(entries.length);
1158 var entryOffsets = new Float32Array(entries.length);
1159 var entryTitles = new Array(entries.length);
1160 var entryDeoptFlags = new Uint8Array(entries.length);
1162 for (var i = 0; i < entries.length; ++i) {
1163 var entry = entries[i];
1164 entryNodes[i] = entry.node;
1165 entryColorIndexes[i] = colorPair.index;
1166 entryLevels[i] = entry.depth;
1167 entryTotalTimes[i] = entry.duration;
1168 entrySelfTimes[i] = entry.selfTime;
1169 entryOffsets[i] = entry.startTime;
1170 entryTitles[i] = entry.node.functionName;
1171 var reason = entry.node.deoptReason;
1172 entryDeoptFlags[i] = (reason && reason !== "no reason");
1175 this._timelineData = {
1176 maxStackDepth: Math.max(maxDepth, depth),
1177 totalTime: this._cpuProfileView.profileHead.totalTime,
1178 entryNodes: entryNodes,
1179 entryColorIndexes: entryColorIndexes,
1180 entryLevels: entryLevels,
1181 entryTotalTimes: entryTotalTimes,
1182 entrySelfTimes: entrySelfTimes,
1183 entryOffsets: entryOffsets,
1184 colorEntryIndexes: colorEntryIndexes,
1185 entryTitles: entryTitles,
1186 entryDeoptFlags: entryDeoptFlags
1189 return this._timelineData;
1193 * @param {number} ms
1196 _millisecondsToString: function(ms)
1201 return WebInspector.UIString("%.1f\u2009ms", ms);
1202 return Number.secondsToString(ms / 1000, true);
1206 * @param {number} entryIndex
1207 * @return {?Array.<!{title: string, text: string}>}
1209 prepareHighlightedEntryInfo: function(entryIndex)
1211 var timelineData = this._timelineData;
1212 var node = timelineData.entryNodes[entryIndex];
1217 function pushEntryInfoRow(title, text)
1222 entryInfo.push(row);
1225 pushEntryInfoRow(WebInspector.UIString("Name"), timelineData.entryTitles[entryIndex]);
1226 var selfTime = this._millisecondsToString(timelineData.entrySelfTimes[entryIndex]);
1227 var totalTime = this._millisecondsToString(timelineData.entryTotalTimes[entryIndex]);
1228 pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime);
1229 pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime);
1231 pushEntryInfoRow(WebInspector.UIString("URL"), node.url + ":" + node.lineNumber);
1232 pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true));
1233 pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true));
1234 if (node.deoptReason && node.deoptReason !== "no reason")
1235 pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptReason);
1241 * @param {number} entryIndex
1244 canJumpToEntry: function(entryIndex)
1246 return this._timelineData.entryNodes[entryIndex].scriptId !== "0";
1250 * @param {number} entryIndex
1253 entryData: function(entryIndex)
1255 return this._timelineData.entryNodes[entryIndex];