blob: b95dc216a6838b77c0fc41cf3c271a9e54fdf790 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @constructor
* @implements {WebInspector.Searchable}
* @extends {WebInspector.ProfileView}
* @param {!WebInspector.SamplingHeapProfileHeader} profileHeader
*/
WebInspector.HeapProfileView = function(profileHeader)
{
this._profileHeader = profileHeader;
this.profile = new WebInspector.SamplingHeapProfileModel(profileHeader._profile || profileHeader.protocolProfile());
this.adjustedTotal = this.profile.total;
var views = [
WebInspector.ProfileView.ViewTypes.Flame,
WebInspector.ProfileView.ViewTypes.Heavy,
WebInspector.ProfileView.ViewTypes.Tree
];
WebInspector.ProfileView.call(this, new WebInspector.HeapProfileView.NodeFormatter(this), views);
}
WebInspector.HeapProfileView.prototype = {
/**
* @override
* @param {string} columnId
* @return {string}
*/
columnHeader: function(columnId)
{
switch (columnId) {
case "self": return WebInspector.UIString("Self Size (bytes)");
case "total": return WebInspector.UIString("Total Size (bytes)");
}
return "";
},
/**
* @override
* @return {!WebInspector.FlameChartDataProvider}
*/
createFlameChartDataProvider: function()
{
return new WebInspector.HeapFlameChartDataProvider(this.profile, this._profileHeader.target());
},
__proto__: WebInspector.ProfileView.prototype
}
/**
* @constructor
* @extends {WebInspector.ProfileType}
*/
WebInspector.SamplingHeapProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.SamplingHeapProfileType.TypeId, WebInspector.UIString("Record Allocation Profile"));
this._recording = false;
WebInspector.SamplingHeapProfileType.instance = this;
}
WebInspector.SamplingHeapProfileType.TypeId = "SamplingHeap";
WebInspector.SamplingHeapProfileType.prototype = {
/**
* @override
* @return {string}
*/
typeName: function()
{
return "Heap";
},
/**
* @override
* @return {string}
*/
fileExtension: function()
{
return ".heapprofile";
},
get buttonTooltip()
{
return this._recording ? WebInspector.UIString("Stop heap profiling") : WebInspector.UIString("Start heap profiling");
},
/**
* @override
* @return {boolean}
*/
buttonClicked: function()
{
var wasRecording = this._recording;
if (wasRecording)
this.stopRecordingProfile();
else
this.startRecordingProfile();
return !wasRecording;
},
get treeItemTitle()
{
return WebInspector.UIString("ALLOCATION PROFILES");
},
get description()
{
return WebInspector.UIString("Allocation profiles show memory allocations from your JavaScript functions.");
},
startRecordingProfile: function()
{
var target = WebInspector.context.flavor(WebInspector.Target);
if (this._profileBeingRecorded || !target)
return;
var profile = new WebInspector.SamplingHeapProfileHeader(target, this);
this.setProfileBeingRecorded(profile);
WebInspector.targetManager.suspendAllTargets();
this.addProfile(profile);
profile.updateStatus(WebInspector.UIString("Recording\u2026"));
this._recording = true;
target.heapProfilerModel.startSampling();
},
stopRecordingProfile: function()
{
this._recording = false;
if (!this._profileBeingRecorded || !this._profileBeingRecorded.target())
return;
var recordedProfile;
/**
* @param {?HeapProfilerAgent.SamplingHeapProfile} profile
* @this {WebInspector.SamplingHeapProfileType}
*/
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.SamplingHeapProfileType}
*/
function fireEvent()
{
this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile);
}
this._profileBeingRecorded.target().heapProfilerModel.stopSampling()
.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.SamplingHeapProfileHeader(null, this, title);
},
/**
* @override
*/
profileBeingRecordedRemoved: function()
{
this.stopRecordingProfile();
},
__proto__: WebInspector.ProfileType.prototype
}
/**
* @constructor
* @extends {WebInspector.WritableProfileHeader}
* @param {?WebInspector.Target} target
* @param {!WebInspector.SamplingHeapProfileType} type
* @param {string=} title
*/
WebInspector.SamplingHeapProfileHeader = function(target, type, title)
{
WebInspector.WritableProfileHeader.call(this, target, type, title || WebInspector.UIString("Profile %d", type.nextProfileUid()));
}
WebInspector.SamplingHeapProfileHeader.prototype = {
/**
* @override
* @return {!WebInspector.ProfileView}
*/
createView: function()
{
return new WebInspector.HeapProfileView(this);
},
/**
* @return {!HeapProfilerAgent.SamplingHeapProfile}
*/
protocolProfile: function()
{
return this._protocolProfile;
},
__proto__: WebInspector.WritableProfileHeader.prototype
}
/**
* @constructor
* @extends {WebInspector.ProfileNode}
* @param {!HeapProfilerAgent.SamplingHeapProfileNode} node
*/
WebInspector.SamplingHeapProfileNode = function(node)
{
var callFrame = node.callFrame || /** @type {!RuntimeAgent.CallFrame} */ ({
// Backward compatibility for old CpuProfileNode format.
functionName: node["functionName"],
scriptId: node["scriptId"],
url: node["url"],
lineNumber: node["lineNumber"] - 1,
columnNumber: node["columnNumber"] - 1
});
WebInspector.ProfileNode.call(this, callFrame);
this.self = node.selfSize;
}
WebInspector.SamplingHeapProfileNode.prototype = {
__proto__: WebInspector.ProfileNode.prototype
}
/**
* @constructor
* @extends {WebInspector.ProfileTreeModel}
* @param {!HeapProfilerAgent.SamplingHeapProfile} profile
*/
WebInspector.SamplingHeapProfileModel = function(profile)
{
WebInspector.ProfileTreeModel.call(this, this._translateProfileTree(profile.head));
}
WebInspector.SamplingHeapProfileModel.prototype = {
/**
* @param {!HeapProfilerAgent.SamplingHeapProfileNode} root
* @return {!WebInspector.SamplingHeapProfileNode}
*/
_translateProfileTree: function(root)
{
var resultRoot = new WebInspector.SamplingHeapProfileNode(root);
var targetNodeStack = [resultRoot];
var sourceNodeStack = [root];
while (sourceNodeStack.length) {
var sourceNode = sourceNodeStack.pop();
var parentNode = targetNodeStack.pop();
parentNode.children = sourceNode.children.map(child => new WebInspector.SamplingHeapProfileNode(child));
sourceNodeStack.push.apply(sourceNodeStack, sourceNode.children);
targetNodeStack.push.apply(targetNodeStack, parentNode.children);
}
return resultRoot;
},
__proto__: WebInspector.ProfileTreeModel.prototype
}
/**
* @constructor
* @implements {WebInspector.ProfileDataGridNode.Formatter}
* @param {!WebInspector.ProfileView} profileView
*/
WebInspector.HeapProfileView.NodeFormatter = function(profileView)
{
this._profileView = profileView;
}
WebInspector.HeapProfileView.NodeFormatter.prototype = {
/**
* @override
* @param {number} value
* @return {string}
*/
formatValue: function(value)
{
return Number.withThousandsSeparator(value);
},
/**
* @override
* @param {number} value
* @param {!WebInspector.ProfileDataGridNode} node
* @return {string}
*/
formatPercent: function(value, node)
{
return 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.ProfileTreeModel} profile
* @param {?WebInspector.Target} target
*/
WebInspector.HeapFlameChartDataProvider = function(profile, target)
{
WebInspector.ProfileFlameChartDataProvider.call(this, target);
this._profile = profile;
}
WebInspector.HeapFlameChartDataProvider.prototype = {
/**
* @override
* @return {number}
*/
minimumBoundary: function()
{
return 0;
},
/**
* @override
* @return {number}
*/
totalTime: function()
{
return this._profile.root.total;
},
/**
* @override
* @param {number} value
* @param {number=} precision
* @return {string}
*/
formatValue: function(value, precision)
{
return WebInspector.UIString("%s\u2009KB", Number.withThousandsSeparator(value / 1e3));
},
/**
* @override
* @return {!WebInspector.FlameChart.TimelineData}
*/
_calculateTimelineData: function()
{
/**
* @param {!WebInspector.ProfileNode} node
* @return {number}
*/
function nodesCount(node)
{
return node.children.reduce((count, node) => count + nodesCount(node), 1);
}
var count = nodesCount(this._profile.root);
/** @type {!Array<!WebInspector.ProfileNode>} */
var entryNodes = new Array(count);
var entryLevels = new Uint8Array(count);
var entryTotalTimes = new Float32Array(count);
var entryStartTimes = new Float64Array(count);
var depth = 0;
var maxDepth = 0;
var position = 0;
var index = 0;
/**
* @param {!WebInspector.ProfileNode} node
*/
function addNode(node)
{
var start = position;
entryNodes[index] = node;
entryLevels[index] = depth;
entryTotalTimes[index] = node.total;
entryStartTimes[index] = position;
++index;
++depth;
node.children.forEach(addNode);
--depth;
maxDepth = Math.max(maxDepth, depth);
position = start + node.total;
}
addNode(this._profile.root);
this._maxStackDepth = maxDepth + 1;
this._entryNodes = entryNodes;
this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes, null);
return this._timelineData;
},
/**
* @override
* @param {number} entryIndex
* @return {?Element}
*/
prepareHighlightedEntryInfo: function(entryIndex)
{
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 });
}
pushEntryInfoRow(WebInspector.UIString("Name"), WebInspector.beautifyFunctionName(node.functionName));
pushEntryInfoRow(WebInspector.UIString("Self size"), Number.bytesToString(node.self));
pushEntryInfoRow(WebInspector.UIString("Total size"), Number.bytesToString(node.total));
var linkifier = new WebInspector.Linkifier();
var link = linkifier.maybeLinkifyConsoleCallFrame(this._target, node.callFrame);
if (link)
pushEntryInfoRow(WebInspector.UIString("URL"), link.textContent);
linkifier.dispose();
return WebInspector.ProfileView.buildPopoverTable(entryInfo);
},
__proto__: WebInspector.ProfileFlameChartDataProvider.prototype
}