Commit 1eae2114 authored by Alexei Filippov's avatar Alexei Filippov Committed by Commit Bot

DevTools: Live heap profile drawer panel.

The panel shows current memory usage broke down by URLs. This is put
behind an experiment.

BUG=937880

Change-Id: I101e75037d8f1dcc68d220ff9750100e5c9607d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1536686
Commit-Queue: Alexei Filippov <alph@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644155}
parent 9dab7383
......@@ -522,6 +522,8 @@ all_devtools_files = [
"front_end/profiler/HeapSnapshotView.js",
"front_end/profiler/HeapTimelineOverview.js",
"front_end/profiler/IsolateSelector.js",
"front_end/profiler/LiveHeapProfileView.js",
"front_end/profiler/liveHeapProfile.css",
"front_end/profiler/module.json",
"front_end/profiler/ProfileDataGrid.js",
"front_end/profiler/ProfileHeader.js",
......@@ -1197,7 +1199,9 @@ visibility = [ "//third_party/blink/*" ]
group("devtools_all_files") {
data = all_devtools_files
deps = [":devtools_frontend_resources_data"]
deps = [
":devtools_frontend_resources_data",
]
}
devtools_frontend_resources_deps = [
......
// Copyright 2019 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.
/**
* @extends {UI.VBox}
*/
Profiler.LiveHeapProfileView = class extends UI.VBox {
constructor() {
super(true);
/** @type {!Map<string, !Profiler.LiveHeapProfileView.GridNode>} */
this._gridNodeByUrl = new Map();
this.registerRequiredCSS('profiler/liveHeapProfile.css');
const columns = [
{
id: 'size',
title: ls`JS Heap`,
width: '72px',
fixedWidth: true,
sortable: true,
align: DataGrid.DataGrid.Align.Right,
sort: DataGrid.DataGrid.Order.Descending
},
{id: 'url', title: ls`Script URL`, fixedWidth: false, sortable: true}
];
this._dataGrid = new DataGrid.SortableDataGrid(columns);
this._dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last);
this._dataGrid.element.classList.add('flex-auto');
this._dataGrid.element.addEventListener('keydown', this._onKeyDown.bind(this), false);
this._dataGrid.addEventListener(DataGrid.DataGrid.Events.OpenedNode, this._revealSourceForSelectedNode, this);
this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._sortingChanged, this);
this._dataGrid.asWidget().show(this.contentElement);
this._pollTimer = setTimeout(() => this._poll());
}
/**
* @override
*/
wasShown() {
this._poll();
}
/**
* @override
*/
willHide() {
clearTimeout(this._pollTimer);
this._pollTimer = 0;
}
async _poll() {
const models = SDK.targetManager.models(SDK.HeapProfilerModel);
const profiles = await Promise.all(models.map(model => model.getSamplingProfile()));
if (!this._pollTimer)
return;
profiles.remove(null);
this._update(profiles);
this._pollTimer = setTimeout(() => this._poll(), 3000);
}
/**
* @param {!Array<!Protocol.HeapProfiler.SamplingHeapProfile>} profiles
*/
_update(profiles) {
/** @type {!Map<string, number>} */
const sizeByUrl = new Map();
for (const profile of profiles)
processNode('', profile.head);
const rootNode = this._dataGrid.rootNode();
const exisitingNodes = new Set();
for (const pair of sizeByUrl) {
const url = /** @type {string} */ (pair[0]);
const size = /** @type {number} */ (pair[1]);
if (!url) {
console.info(`Node with empty URL: ${size} bytes`);
continue;
}
let node = this._gridNodeByUrl.get(url);
if (node) {
node.updateSize(size);
} else {
node = new Profiler.LiveHeapProfileView.GridNode(url, size);
this._gridNodeByUrl.set(url, node);
rootNode.appendChild(node);
}
exisitingNodes.add(node);
}
for (const node of rootNode.children.slice()) {
if (!exisitingNodes.has(node))
node.remove();
this._gridNodeByUrl.delete(node);
}
this._sortingChanged();
/**
* @param {string} parentUrl
* @param {!Protocol.HeapProfiler.SamplingHeapProfileNode} node
*/
function processNode(parentUrl, node) {
const url = node.callFrame.url || parentUrl || systemNodeName(node) || anonymousScriptName(node);
if (node.selfSize)
sizeByUrl.set(url, (sizeByUrl.get(url) || 0) + node.selfSize);
node.children.forEach(child => processNode(url, child));
}
/**
* @param {!Protocol.HeapProfiler.SamplingHeapProfileNode} node
* @return {string}
*/
function systemNodeName(node) {
const name = node.callFrame.functionName;
return name.startsWith('(') && name !== '(root)' ? name : '';
}
/**
* @param {!Protocol.HeapProfiler.SamplingHeapProfileNode} node
* @return {string}
*/
function anonymousScriptName(node) {
return Number(node.callFrame.scriptId) ? Common.UIString('(Anonymous Script %s)', node.callFrame.scriptId) : '';
}
}
/**
* @param {!Event} event
*/
_onKeyDown(event) {
if (!isEnterKey(event))
return;
event.consume(true);
this._revealSourceForSelectedNode();
}
_revealSourceForSelectedNode() {
const node = this._dataGrid.selectedNode;
if (!node || !node._url)
return;
const sourceCode = Workspace.workspace.uiSourceCodeForURL(node._url);
if (sourceCode)
Common.Revealer.reveal(sourceCode);
}
_sortingChanged() {
const columnId = this._dataGrid.sortColumnId();
if (!columnId)
return;
const sortByUrl = (a, b) => b._url.localeCompare(a._url);
const sortBySize = (a, b) => b._size - a._size;
const sortFunction = columnId === 'url' ? sortByUrl : sortBySize;
this._dataGrid.sortNodes(sortFunction, this._dataGrid.isSortOrderAscending());
}
};
Profiler.LiveHeapProfileView.GridNode = class extends DataGrid.SortableDataGridNode {
/**
* @param {string} url
* @param {number} size
*/
constructor(url, size) {
super();
this._url = url;
this._size = size;
}
/**
* @param {number} size
*/
updateSize(size) {
if (this._size === size)
return;
this._size = size;
this.refresh();
}
/**
* @override
* @param {string} columnId
* @return {!Element}
*/
createCell(columnId) {
const cell = this.createTD(columnId);
switch (columnId) {
case 'url':
cell.title = this._url;
cell.textContent = this._url;
break;
case 'size':
cell.textContent = Number.withThousandsSeparator(Math.round(this._size / 1e3));
cell.createChild('span', 'size-units').textContent = ls`KB`;
break;
}
return cell;
}
};
/*
* Copyright 2019 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.
*/
.data-grid {
border: none;
}
.data-grid td .size-units {
margin-left: 4px;
font-size: 75%;
}
.data-grid tr:not(.selected) td .size-units {
color: #999;
}
......@@ -8,6 +8,16 @@
"order": 60,
"className": "Profiler.HeapProfilerPanel"
},
{
"type": "view",
"location": "drawer-view",
"id": "live_heap_profile",
"title": "Live Heap Profile",
"persistence": "closeable",
"className": "Profiler.LiveHeapProfileView",
"order": 100,
"experiment": "liveHeapProfile"
},
{
"type": "@UI.ContextMenu.Provider",
"contextTypes": [
......@@ -99,11 +109,13 @@
"HeapSnapshotView.js",
"HeapTimelineOverview.js",
"IsolateSelector.js",
"LiveHeapProfileView.js",
"ProfileLauncherView.js",
"ProfileTypeRegistry.js"
],
"resources": [
"heapProfiler.css",
"liveHeapProfile.css",
"profileLauncherView.css",
"profilesPanel.css",
"profilesSidebarTree.css"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment