blob: e06aef704002c54051240086825acccf5eca02ae [file] [log] [blame]
/*
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @implements {WebInspector.Searchable}
* @extends {WebInspector.ProfileView}
* @param {!WebInspector.CPUProfileHeader} profileHeader
*/
WebInspector.CPUProfileView = function(profileHeader)
{
this._profileHeader = profileHeader;
this.profile = new WebInspector.CPUProfileDataModel(profileHeader._profile || profileHeader.protocolProfile());
this.adjustedTotal = this.profile.profileHead.total;
this.adjustedTotal -= this.profile.idleNode ? this.profile.idleNode.total : 0;
WebInspector.ProfileView.call(this, new WebInspector.CPUProfileView.NodeFormatter(this));
}
WebInspector.CPUProfileView.prototype = {
/**
* @override
*/
wasShown: function()
{
WebInspector.ProfileView.prototype.wasShown.call(this);
var lineLevelProfile = WebInspector.LineLevelProfile.instance();
lineLevelProfile.reset();
lineLevelProfile.appendCPUProfile(this.profile);
},
/**
* @override
* @param {string} columnId
* @return {string}
*/
columnHeader: function(columnId)
{
switch (columnId) {
case "self": return WebInspector.UIString("Self Time");
case "total": return WebInspector.UIString("Total Time");
}
return "";
},
/**
* @override
* @return {!WebInspector.FlameChartDataProvider}
*/
createFlameChartDataProvider: function()
{
return new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target());
},
__proto__: WebInspector.ProfileView.prototype
}
/**
* @constructor
* @extends {WebInspector.ProfileType}
*/
WebInspector.CPUProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Record JavaScript CPU Profile"));
this._recording = false;
this._nextAnonymousConsoleProfileNumber = 1;
this._anonymousConsoleProfileIdToTitle = {};
WebInspector.CPUProfileType.instance = this;
WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.Events.ConsoleProfileStarted, this._consoleProfileStarted, this);
WebInspector.targetManager.addModelListener(WebInspector.CPUProfilerModel, WebInspector.CPUProfilerModel.Events.ConsoleProfileFinished, this._consoleProfileFinished, this);
}
WebInspector.CPUProfileType.TypeId = "CPU";
WebInspector.CPUProfileType.prototype = {
/**
* @override
* @return {string}
*/
typeName: function()
{
return "CPU";
},
/**
* @override
* @return {string}
*/
fileExtension: function()
{
return ".cpuprofile";
},
get buttonTooltip()
{
return this._recording ? WebInspector.UIString("Stop CPU profiling") : WebInspector.UIString("Start CPU profiling");
},
/**
* @override
* @return {boolean}
*/
buttonClicked: function()
{
if (this._recording) {
this.stopRecordingProfile();
return false;
} else {
this.startRecordingProfile();
return true;
}
},
get treeItemTitle()
{
return WebInspector.UIString("CPU PROFILES");
},
get description()
{
return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
},
/**
* @param {!WebInspector.Event} event
*/
_consoleProfileStarted: function(event)
{
var data = /** @type {!WebInspector.CPUProfilerModel.EventData} */ (event.data);
var resolvedTitle = data.title;
if (!resolvedTitle) {
resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++);
this._anonymousConsoleProfileIdToTitle[data.id] = resolvedTitle;
}
this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, data.scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle));
},
/**
* @param {!WebInspector.Event} event
*/
_consoleProfileFinished: function(event)
{
var data = /** @type {!WebInspector.CPUProfilerModel.EventData} */ (event.data);
var cpuProfile = /** @type {!ProfilerAgent.Profile} */ (data.cpuProfile);
var resolvedTitle = data.title;
if (typeof resolvedTitle === "undefined") {
resolvedTitle = this._anonymousConsoleProfileIdToTitle[data.id];
delete this._anonymousConsoleProfileIdToTitle[data.id];
}
var profile = new WebInspector.CPUProfileHeader(data.scriptLocation.target(), this, resolvedTitle);
profile.setProtocolProfile(cpuProfile);
this.addProfile(profile);
this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, data.scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle));
},
/**
* @param {string} type
* @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {string} messageText
*/
_addMessageToConsole: function(type, scriptLocation, messageText)
{
var script = scriptLocation.script();
var target = scriptLocation.target();
var message = new WebInspector.ConsoleMessage(
target,
WebInspector.ConsoleMessage.MessageSource.ConsoleAPI,
WebInspector.ConsoleMessage.MessageLevel.Debug,
messageText,
type,
undefined,
undefined,
undefined,
undefined,
[{
functionName: "",
scriptId: scriptLocation.scriptId,
url: script ? script.contentURL() : "",
lineNumber: scriptLocation.lineNumber,
columnNumber: scriptLocation.columnNumber || 0
}]);
target.consoleModel.addMessage(message);
},
startRecordingProfile: function()
{
var target = WebInspector.context.flavor(WebInspector.Target);
if (this._profileBeingRecorded || !target)
return;
var profile = new WebInspector.CPUProfileHeader(target, this);
this.setProfileBeingRecorded(profile);
WebInspector.targetManager.suspendAllTargets();
this.addProfile(profile);
profile.updateStatus(WebInspector.UIString("Recording\u2026"));
this._recording = true;
target.cpuProfilerModel.startRecording();
},
stopRecordingProfile: function()
{
this._recording = false;
if (!this._profileBeingRecorded || !this._profileBeingRecorded.target())
return;
var recordedProfile;
/**
* @param {?ProfilerAgent.Profile} profile
* @this {WebInspector.CPUProfileType}
*/
function didStopProfiling(profile)
{
if (!this._profileBeingRecorded)
return;
console.assert(profile);
this._profileBeingRecorded.setProtocolProfile(profile);
this._profileBeingRecorded.updateStatus("");
recordedProfile = this._profileBeingRecorded;
this.setProfileBeingRecorded(null);
}
/**
* @this {WebInspector.CPUProfileType}
*/
function fireEvent()
{
this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile);
}
this._profileBeingRecorded.target().cpuProfilerModel.stopRecording()
.then(didStopProfiling.bind(this))
.then(WebInspector.targetManager.resumeAllTargets.bind(WebInspector.targetManager))
.then(fireEvent.bind(this));
},
/**
* @override
* @param {string} title
* @return {!WebInspector.ProfileHeader}
*/
createProfileLoadedFromFile: function(title)
{
return new WebInspector.CPUProfileHeader(null, this, title);
},
/**
* @override
*/
profileBeingRecordedRemoved: function()
{
this.stopRecordingProfile();
},
__proto__: WebInspector.ProfileType.prototype
}
/**
* @constructor
* @extends {WebInspector.WritableProfileHeader}
* @param {?WebInspector.Target} target
* @param {!WebInspector.CPUProfileType} type
* @param {string=} title
*/
WebInspector.CPUProfileHeader = function(target, type, title)
{
WebInspector.WritableProfileHeader.call(this, target, type, title);
}
WebInspector.CPUProfileHeader.prototype = {
/**
* @override
* @return {!WebInspector.ProfileView}
*/
createView: function()
{
return new WebInspector.CPUProfileView(this);
},
/**
* @return {!ProfilerAgent.Profile}
*/
protocolProfile: function()
{
return this._protocolProfile;
},
__proto__: WebInspector.WritableProfileHeader.prototype
}
/**
* @implements {WebInspector.ProfileDataGridNode.Formatter}
* @constructor
*/
WebInspector.CPUProfileView.NodeFormatter = function(profileView)
{
this._profileView = profileView;
}
WebInspector.CPUProfileView.NodeFormatter.prototype = {
/**
* @override
* @param {number} value
* @return {string}
*/
formatValue: function(value)
{
return WebInspector.UIString("%.1f\u2009ms", value);
},
/**
* @override
* @param {number} value
* @param {!WebInspector.ProfileDataGridNode} node
* @return {string}
*/
formatPercent: function(value, node)
{
return node.profileNode === this._profileView.profile.idleNode ? "" : WebInspector.UIString("%.2f\u2009%%", value);
},
/**
* @override
* @param {!WebInspector.ProfileDataGridNode} node
* @return {?Element}
*/
linkifyNode: function(node)
{
return this._profileView.linkifier().maybeLinkifyConsoleCallFrame(this._profileView.target(), node.profileNode.callFrame, "profile-node-file");
}
}
/**
* @constructor
* @extends {WebInspector.ProfileFlameChartDataProvider}
* @param {!WebInspector.CPUProfileDataModel} cpuProfile
* @param {?WebInspector.Target} target
*/
WebInspector.CPUFlameChartDataProvider = function(cpuProfile, target)
{
WebInspector.ProfileFlameChartDataProvider.call(this, target);
this._cpuProfile = cpuProfile;
}
WebInspector.CPUFlameChartDataProvider.prototype = {
/**
* @override
* @return {!WebInspector.FlameChart.TimelineData}
*/
_calculateTimelineData: function()
{
/**
* @constructor
* @param {number} depth
* @param {number} duration
* @param {number} startTime
* @param {number} selfTime
* @param {!WebInspector.CPUProfileNode} node
*/
function ChartEntry(depth, duration, startTime, selfTime, node)
{
this.depth = depth;
this.duration = duration;
this.startTime = startTime;
this.selfTime = selfTime;
this.node = node;
}
/** @type {!Array.<?ChartEntry>} */
var entries = [];
/** @type {!Array.<number>} */
var stack = [];
var maxDepth = 5;
function onOpenFrame()
{
stack.push(entries.length);
// Reserve space for the entry, as they have to be ordered by startTime.
// The entry itself will be put there in onCloseFrame.
entries.push(null);
}
/**
* @param {number} depth
* @param {!WebInspector.CPUProfileNode} node
* @param {number} startTime
* @param {number} totalTime
* @param {number} selfTime
*/
function onCloseFrame(depth, node, startTime, totalTime, selfTime)
{
var index = stack.pop();
entries[index] = new ChartEntry(depth, totalTime, startTime, selfTime, node);
maxDepth = Math.max(maxDepth, depth);
}
this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame);
/** @type {!Array<!WebInspector.CPUProfileNode>} */
var entryNodes = new Array(entries.length);
var entryLevels = new Uint8Array(entries.length);
var entryTotalTimes = new Float32Array(entries.length);
var entrySelfTimes = new Float32Array(entries.length);
var entryStartTimes = new Float64Array(entries.length);
var minimumBoundary = this.minimumBoundary();
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
entryNodes[i] = entry.node;
entryLevels[i] = entry.depth;
entryTotalTimes[i] = entry.duration;
entryStartTimes[i] = entry.startTime;
entrySelfTimes[i] = entry.selfTime;
}
this._maxStackDepth = maxDepth;
this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes, null);
/** @type {!Array<!WebInspector.CPUProfileNode>} */
this._entryNodes = entryNodes;
this._entrySelfTimes = entrySelfTimes;
return this._timelineData;
},
/**
* @override
* @param {number} entryIndex
* @return {?Element}
*/
prepareHighlightedEntryInfo: function(entryIndex)
{
var timelineData = this._timelineData;
var node = this._entryNodes[entryIndex];
if (!node)
return null;
var entryInfo = [];
/**
* @param {string} title
* @param {string} value
*/
function pushEntryInfoRow(title, value)
{
entryInfo.push({ title: title, value: value });
}
/**
* @param {number} ms
* @return {string}
*/
function millisecondsToString(ms)
{
if (ms === 0)
return "0";
if (ms < 1000)
return WebInspector.UIString("%.1f\u2009ms", ms);
return Number.secondsToString(ms / 1000, true);
}
var name = WebInspector.beautifyFunctionName(node.functionName);
pushEntryInfoRow(WebInspector.UIString("Name"), name);
var selfTime = millisecondsToString(this._entrySelfTimes[entryIndex]);
var totalTime = millisecondsToString(timelineData.entryTotalTimes[entryIndex]);
pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime);
pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime);
var linkifier = new WebInspector.Linkifier();
var link = linkifier.maybeLinkifyConsoleCallFrame(this._target, node.callFrame);
if (link)
pushEntryInfoRow(WebInspector.UIString("URL"), link.textContent);
linkifier.dispose();
pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.self / 1000, true));
pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.total / 1000, true));
if (node.deoptReason)
pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptReason);
return WebInspector.ProfileView.buildPopoverTable(entryInfo);
},
__proto__: WebInspector.ProfileFlameChartDataProvider.prototype
}