blob: 010d83c37efd5e4aa0df8684671fa18ee7e5345c [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.
*/
/**
* @implements {UI.Searchable}
* @unrestricted
*/
Profiler.CPUProfileView = class extends Profiler.ProfileView {
/**
* @param {!Profiler.CPUProfileHeader} profileHeader
*/
constructor(profileHeader) {
super();
this._profileHeader = profileHeader;
this.profile = new SDK.CPUProfileDataModel(profileHeader._profile || profileHeader.protocolProfile());
this.adjustedTotal = this.profile.profileHead.total;
this.adjustedTotal -= this.profile.idleNode ? this.profile.idleNode.total : 0;
this.initialize(new Profiler.CPUProfileView.NodeFormatter(this));
}
/**
* @override
*/
wasShown() {
super.wasShown();
var lineLevelProfile = Components.LineLevelProfile.instance();
lineLevelProfile.reset();
lineLevelProfile.appendCPUProfile(this.profile);
}
/**
* @override
* @param {string} columnId
* @return {string}
*/
columnHeader(columnId) {
switch (columnId) {
case 'self':
return Common.UIString('Self Time');
case 'total':
return Common.UIString('Total Time');
}
return '';
}
/**
* @override
* @return {!UI.FlameChartDataProvider}
*/
createFlameChartDataProvider() {
return new Profiler.CPUFlameChartDataProvider(this.profile, this._profileHeader.target());
}
};
/**
* @unrestricted
*/
Profiler.CPUProfileType = class extends Profiler.ProfileType {
constructor() {
super(Profiler.CPUProfileType.TypeId, Common.UIString('Record JavaScript CPU Profile'));
this._recording = false;
this._nextAnonymousConsoleProfileNumber = 1;
this._anonymousConsoleProfileIdToTitle = {};
Profiler.CPUProfileType.instance = this;
SDK.targetManager.addModelListener(
SDK.CPUProfilerModel, SDK.CPUProfilerModel.Events.ConsoleProfileStarted, this._consoleProfileStarted, this);
SDK.targetManager.addModelListener(
SDK.CPUProfilerModel, SDK.CPUProfilerModel.Events.ConsoleProfileFinished, this._consoleProfileFinished, this);
}
/**
* @override
* @return {string}
*/
typeName() {
return 'CPU';
}
/**
* @override
* @return {string}
*/
fileExtension() {
return '.cpuprofile';
}
get buttonTooltip() {
return this._recording ? Common.UIString('Stop CPU profiling') : Common.UIString('Start CPU profiling');
}
/**
* @override
* @return {boolean}
*/
buttonClicked() {
if (this._recording) {
this.stopRecordingProfile();
return false;
} else {
this.startRecordingProfile();
return true;
}
}
get treeItemTitle() {
return Common.UIString('CPU PROFILES');
}
get description() {
return Common.UIString('CPU profiles show where the execution time is spent in your page\'s JavaScript functions.');
}
/**
* @param {!Common.Event} event
*/
_consoleProfileStarted(event) {
var data = /** @type {!SDK.CPUProfilerModel.EventData} */ (event.data);
var resolvedTitle = data.title;
if (!resolvedTitle) {
resolvedTitle = Common.UIString('Profile %s', this._nextAnonymousConsoleProfileNumber++);
this._anonymousConsoleProfileIdToTitle[data.id] = resolvedTitle;
}
this._addMessageToConsole(
SDK.ConsoleMessage.MessageType.Profile, data.scriptLocation,
Common.UIString('Profile \'%s\' started.', resolvedTitle));
}
/**
* @param {!Common.Event} event
*/
_consoleProfileFinished(event) {
var data = /** @type {!SDK.CPUProfilerModel.EventData} */ (event.data);
var cpuProfile = /** @type {!Protocol.Profiler.Profile} */ (data.cpuProfile);
var resolvedTitle = data.title;
if (typeof resolvedTitle === 'undefined') {
resolvedTitle = this._anonymousConsoleProfileIdToTitle[data.id];
delete this._anonymousConsoleProfileIdToTitle[data.id];
}
var profile = new Profiler.CPUProfileHeader(data.scriptLocation.target(), this, resolvedTitle);
profile.setProtocolProfile(cpuProfile);
this.addProfile(profile);
this._addMessageToConsole(
SDK.ConsoleMessage.MessageType.ProfileEnd, data.scriptLocation,
Common.UIString('Profile \'%s\' finished.', resolvedTitle));
}
/**
* @param {string} type
* @param {!SDK.DebuggerModel.Location} scriptLocation
* @param {string} messageText
*/
_addMessageToConsole(type, scriptLocation, messageText) {
var script = scriptLocation.script();
var target = scriptLocation.target();
var message = new SDK.ConsoleMessage(
target, SDK.ConsoleMessage.MessageSource.ConsoleAPI, SDK.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() {
var target = UI.context.flavor(SDK.Target);
if (this._profileBeingRecorded || !target)
return;
var profile = new Profiler.CPUProfileHeader(target, this);
this.setProfileBeingRecorded(profile);
SDK.targetManager.suspendAllTargets();
this.addProfile(profile);
profile.updateStatus(Common.UIString('Recording\u2026'));
this._recording = true;
target.cpuProfilerModel.startRecording();
}
stopRecordingProfile() {
this._recording = false;
if (!this._profileBeingRecorded || !this._profileBeingRecorded.target())
return;
var recordedProfile;
/**
* @param {?Protocol.Profiler.Profile} profile
* @this {Profiler.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 {Profiler.CPUProfileType}
*/
function fireEvent() {
this.dispatchEventToListeners(Profiler.ProfileType.Events.ProfileComplete, recordedProfile);
}
this._profileBeingRecorded.target()
.cpuProfilerModel.stopRecording()
.then(didStopProfiling.bind(this))
.then(SDK.targetManager.resumeAllTargets.bind(SDK.targetManager))
.then(fireEvent.bind(this));
}
/**
* @override
* @param {string} title
* @return {!Profiler.ProfileHeader}
*/
createProfileLoadedFromFile(title) {
return new Profiler.CPUProfileHeader(null, this, title);
}
/**
* @override
*/
profileBeingRecordedRemoved() {
this.stopRecordingProfile();
}
};
Profiler.CPUProfileType.TypeId = 'CPU';
/**
* @unrestricted
*/
Profiler.CPUProfileHeader = class extends Profiler.WritableProfileHeader {
/**
* @param {?SDK.Target} target
* @param {!Profiler.CPUProfileType} type
* @param {string=} title
*/
constructor(target, type, title) {
super(target, type, title);
}
/**
* @override
* @return {!Profiler.ProfileView}
*/
createView() {
return new Profiler.CPUProfileView(this);
}
/**
* @return {!Protocol.Profiler.Profile}
*/
protocolProfile() {
return this._protocolProfile;
}
};
/**
* @implements {Profiler.ProfileDataGridNode.Formatter}
* @unrestricted
*/
Profiler.CPUProfileView.NodeFormatter = class {
constructor(profileView) {
this._profileView = profileView;
}
/**
* @override
* @param {number} value
* @return {string}
*/
formatValue(value) {
return Common.UIString('%.1f\u2009ms', value);
}
/**
* @override
* @param {number} value
* @param {!Profiler.ProfileDataGridNode} node
* @return {string}
*/
formatPercent(value, node) {
return node.profileNode === this._profileView.profile.idleNode ? '' : Common.UIString('%.2f\u2009%%', value);
}
/**
* @override
* @param {!Profiler.ProfileDataGridNode} node
* @return {?Element}
*/
linkifyNode(node) {
return this._profileView.linkifier().maybeLinkifyConsoleCallFrame(
this._profileView.target(), node.profileNode.callFrame, 'profile-node-file');
}
};
/**
* @unrestricted
*/
Profiler.CPUFlameChartDataProvider = class extends Profiler.ProfileFlameChartDataProvider {
/**
* @param {!SDK.CPUProfileDataModel} cpuProfile
* @param {?SDK.Target} target
*/
constructor(cpuProfile, target) {
super(target);
this._cpuProfile = cpuProfile;
}
/**
* @override
* @return {!UI.FlameChart.TimelineData}
*/
_calculateTimelineData() {
/** @type {!Array.<?Profiler.CPUFlameChartDataProvider.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 {!SDK.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 Profiler.CPUFlameChartDataProvider.ChartEntry(depth, totalTime, startTime, selfTime, node);
maxDepth = Math.max(maxDepth, depth);
}
this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame);
/** @type {!Array<!SDK.CPUProfileNode>} */
var entryNodes = new Array(entries.length);
var entryLevels = new Uint16Array(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 UI.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes, null);
/** @type {!Array<!SDK.CPUProfileNode>} */
this._entryNodes = entryNodes;
this._entrySelfTimes = entrySelfTimes;
return this._timelineData;
}
/**
* @override
* @param {number} entryIndex
* @return {?Element}
*/
prepareHighlightedEntryInfo(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 Common.UIString('%.1f\u2009ms', ms);
return Number.secondsToString(ms / 1000, true);
}
var name = UI.beautifyFunctionName(node.functionName);
pushEntryInfoRow(Common.UIString('Name'), name);
var selfTime = millisecondsToString(this._entrySelfTimes[entryIndex]);
var totalTime = millisecondsToString(timelineData.entryTotalTimes[entryIndex]);
pushEntryInfoRow(Common.UIString('Self time'), selfTime);
pushEntryInfoRow(Common.UIString('Total time'), totalTime);
var linkifier = new Components.Linkifier();
var link = linkifier.maybeLinkifyConsoleCallFrame(this._target, node.callFrame);
if (link)
pushEntryInfoRow(Common.UIString('URL'), link.textContent);
linkifier.dispose();
pushEntryInfoRow(Common.UIString('Aggregated self time'), Number.secondsToString(node.self / 1000, true));
pushEntryInfoRow(Common.UIString('Aggregated total time'), Number.secondsToString(node.total / 1000, true));
if (node.deoptReason)
pushEntryInfoRow(Common.UIString('Not optimized'), node.deoptReason);
return Profiler.ProfileView.buildPopoverTable(entryInfo);
}
};
/**
* @unrestricted
*/
Profiler.CPUFlameChartDataProvider.ChartEntry = class {
/**
* @param {number} depth
* @param {number} duration
* @param {number} startTime
* @param {number} selfTime
* @param {!SDK.CPUProfileNode} node
*/
constructor(depth, duration, startTime, selfTime, node) {
this.depth = depth;
this.duration = duration;
this.startTime = startTime;
this.selfTime = selfTime;
this.node = node;
}
};