Commit f1092ff4 authored by Alexei Filippov's avatar Alexei Filippov Committed by Commit Bot

DevTools: Make line level profile support memory annotations.

BUG=937880

Change-Id: Ib222ad6cddf23b0f28863e75f1bc729e638c6ee4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1504460
Commit-Queue: Alexei Filippov <alph@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638307}
parent b604a31e
...@@ -2,27 +2,15 @@ ...@@ -2,27 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
PerfUI.LineLevelProfile = class { PerfUI.LineLevelProfile = {};
PerfUI.LineLevelProfile.Performance = class {
constructor() { constructor() {
this._locationPool = new Bindings.LiveLocationPool(); this._helper = new PerfUI.LineLevelProfile._Helper('performance');
this._updateTimer = null;
this.reset();
} }
reset() { reset() {
// The second map uses string keys for script URLs and numbers for scriptId. this._helper.reset();
/** @type {!Map<?SDK.Target, !Map<string|number, !Map<number, number>>>} */
this._lineData = new Map();
this._scheduleUpdate();
}
/**
* @return {!PerfUI.LineLevelProfile}
*/
static instance() {
if (!PerfUI.LineLevelProfile._instance)
PerfUI.LineLevelProfile._instance = new PerfUI.LineLevelProfile();
return PerfUI.LineLevelProfile._instance;
} }
/** /**
...@@ -30,11 +18,6 @@ PerfUI.LineLevelProfile = class { ...@@ -30,11 +18,6 @@ PerfUI.LineLevelProfile = class {
*/ */
_appendLegacyCPUProfile(profile) { _appendLegacyCPUProfile(profile) {
const target = profile.target(); const target = profile.target();
let dataByTarget = this._lineData.get(target);
if (!dataByTarget) {
dataByTarget = new Map();
this._lineData.set(target, dataByTarget);
}
const nodesToGo = [profile.profileHead]; const nodesToGo = [profile.profileHead];
const sampleDuration = (profile.profileEndTime - profile.profileStartTime) / profile.totalHitCount; const sampleDuration = (profile.profileEndTime - profile.profileStartTime) / profile.totalHitCount;
while (nodesToGo.length) { while (nodesToGo.length) {
...@@ -44,16 +27,11 @@ PerfUI.LineLevelProfile = class { ...@@ -44,16 +27,11 @@ PerfUI.LineLevelProfile = class {
nodesToGo.push(node); nodesToGo.push(node);
if (!node.url || !node.positionTicks) if (!node.url || !node.positionTicks)
continue; continue;
let fileInfo = dataByTarget.get(node.url);
if (!fileInfo) {
fileInfo = new Map();
dataByTarget.set(node.url, fileInfo);
}
for (let j = 0; j < node.positionTicks.length; ++j) { for (let j = 0; j < node.positionTicks.length; ++j) {
const lineInfo = node.positionTicks[j]; const lineInfo = node.positionTicks[j];
const line = lineInfo.line; const line = lineInfo.line;
const time = lineInfo.ticks * sampleDuration; const time = lineInfo.ticks * sampleDuration;
fileInfo.set(line, (fileInfo.get(line) || 0) + time); this._helper.addLineData(target, node.url, line, time);
} }
} }
} }
...@@ -65,15 +43,10 @@ PerfUI.LineLevelProfile = class { ...@@ -65,15 +43,10 @@ PerfUI.LineLevelProfile = class {
appendCPUProfile(profile) { appendCPUProfile(profile) {
if (!profile.lines) { if (!profile.lines) {
this._appendLegacyCPUProfile(profile); this._appendLegacyCPUProfile(profile);
this._scheduleUpdate(); this._helper.scheduleUpdate();
return; return;
} }
const target = profile.target(); const target = profile.target();
let dataByTarget = this._lineData.get(target);
if (!dataByTarget) {
dataByTarget = new Map();
this._lineData.set(target, dataByTarget);
}
for (let i = 1; i < profile.samples.length; ++i) { for (let i = 1; i < profile.samples.length; ++i) {
const line = profile.lines[i]; const line = profile.lines[i];
if (!line) if (!line)
...@@ -82,18 +55,104 @@ PerfUI.LineLevelProfile = class { ...@@ -82,18 +55,104 @@ PerfUI.LineLevelProfile = class {
const scriptIdOrUrl = node.scriptId || node.url; const scriptIdOrUrl = node.scriptId || node.url;
if (!scriptIdOrUrl) if (!scriptIdOrUrl)
continue; continue;
let dataByScript = dataByTarget.get(scriptIdOrUrl);
if (!dataByScript) {
dataByScript = new Map();
dataByTarget.set(scriptIdOrUrl, dataByScript);
}
const time = profile.timestamps[i] - profile.timestamps[i - 1]; const time = profile.timestamps[i] - profile.timestamps[i - 1];
dataByScript.set(line, (dataByScript.get(line) || 0) + time); this._helper.addLineData(target, scriptIdOrUrl, line, time);
}
this._helper.scheduleUpdate();
}
/**
* @return {!PerfUI.LineLevelProfile.Performance}
*/
static instance() {
if (!PerfUI.LineLevelProfile.Performance._instance)
PerfUI.LineLevelProfile.Performance._instance = new PerfUI.LineLevelProfile.Performance();
return PerfUI.LineLevelProfile.Performance._instance;
}
};
PerfUI.LineLevelProfile.Memory = class {
constructor() {
this._helper = new PerfUI.LineLevelProfile._Helper('memory');
}
reset() {
this._helper.reset();
}
/**
* @param {!Protocol.HeapProfiler.SamplingHeapProfile} profile
* @param {?SDK.Target} target
*/
appendHeapProfile(profile, target) {
const helper = this._helper;
processNode(profile.head);
helper.scheduleUpdate();
/**
* @param {!Protocol.HeapProfiler.SamplingHeapProfileNode} node
*/
function processNode(node) {
node.children.forEach(processNode);
if (!node.selfSize)
return;
const script = Number(node.callFrame.scriptId) || node.callFrame.url;
if (!script)
return;
const line = node.callFrame.lineNumber + 1;
helper.addLineData(target, script, line, node.selfSize);
} }
this._scheduleUpdate();
} }
_scheduleUpdate() { /**
* @return {!PerfUI.LineLevelProfile.Memory}
*/
static instance() {
if (!PerfUI.LineLevelProfile.Memory._instance)
PerfUI.LineLevelProfile.Memory._instance = new PerfUI.LineLevelProfile.Memory();
return PerfUI.LineLevelProfile.Memory._instance;
}
};
PerfUI.LineLevelProfile._Helper = class {
/**
* @param {string} type
*/
constructor(type) {
this._type = type;
this._locationPool = new Bindings.LiveLocationPool();
this._updateTimer = null;
this.reset();
}
reset() {
// The second map uses string keys for script URLs and numbers for scriptId.
/** @type {!Map<?SDK.Target, !Map<string|number, !Map<number, number>>>} */
this._lineData = new Map();
this.scheduleUpdate();
}
/**
* @param {?SDK.Target} target
* @param {string|number} scriptIdOrUrl
* @param {number} line
* @param {number} data
*/
addLineData(target, scriptIdOrUrl, line, data) {
let targetData = this._lineData.get(target);
if (!targetData) {
targetData = new Map();
this._lineData.set(target, targetData);
}
let scriptData = targetData.get(scriptIdOrUrl);
if (!scriptData) {
scriptData = new Map();
targetData.set(scriptIdOrUrl, scriptData);
}
scriptData.set(line, (scriptData.get(line) || 0) + data);
}
scheduleUpdate() {
if (this._updateTimer) if (this._updateTimer)
return; return;
this._updateTimer = setTimeout(() => { this._updateTimer = setTimeout(() => {
...@@ -104,8 +163,7 @@ PerfUI.LineLevelProfile = class { ...@@ -104,8 +163,7 @@ PerfUI.LineLevelProfile = class {
_doUpdate() { _doUpdate() {
this._locationPool.disposeAll(); this._locationPool.disposeAll();
Workspace.workspace.uiSourceCodes().forEach( Workspace.workspace.uiSourceCodes().forEach(uiSourceCode => uiSourceCode.removeDecorationsForType(this._type));
uiSourceCode => uiSourceCode.removeDecorationsForType(PerfUI.LineLevelProfile.LineDecorator.type));
for (const targetToScript of this._lineData) { for (const targetToScript of this._lineData) {
const target = /** @type {?SDK.Target} */ (targetToScript[0]); const target = /** @type {?SDK.Target} */ (targetToScript[0]);
const debuggerModel = target ? target.model(SDK.DebuggerModel) : null; const debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
...@@ -124,31 +182,31 @@ PerfUI.LineLevelProfile = class { ...@@ -124,31 +182,31 @@ PerfUI.LineLevelProfile = class {
const line = /** @type {number} */ (lineToData[0]) - 1; const line = /** @type {number} */ (lineToData[0]) - 1;
const data = /** @type {number} */ (lineToData[1]); const data = /** @type {number} */ (lineToData[1]);
if (uiSourceCode) { if (uiSourceCode) {
uiSourceCode.addLineDecoration(line, PerfUI.LineLevelProfile.LineDecorator.type, data); uiSourceCode.addLineDecoration(line, this._type, data);
continue; continue;
} }
const rawLocation = typeof scriptIdOrUrl === 'string' ? const rawLocation = typeof scriptIdOrUrl === 'string' ?
debuggerModel.createRawLocationByURL(scriptIdOrUrl, line, 0) : debuggerModel.createRawLocationByURL(scriptIdOrUrl, line, 0) :
debuggerModel.createRawLocationByScriptId(String(scriptIdOrUrl), line, 0); debuggerModel.createRawLocationByScriptId(String(scriptIdOrUrl), line, 0);
if (rawLocation) if (rawLocation)
new PerfUI.LineLevelProfile.Presentation(rawLocation, data, this._locationPool); new PerfUI.LineLevelProfile.Presentation(rawLocation, this._type, data, this._locationPool);
} }
} }
} }
} }
}; };
/**
* @unrestricted
*/
PerfUI.LineLevelProfile.Presentation = class { PerfUI.LineLevelProfile.Presentation = class {
/** /**
* @param {!SDK.DebuggerModel.Location} rawLocation * @param {!SDK.DebuggerModel.Location} rawLocation
* @param {string} type
* @param {number} time * @param {number} time
* @param {!Bindings.LiveLocationPool} locationPool * @param {!Bindings.LiveLocationPool} locationPool
*/ */
constructor(rawLocation, time, locationPool) { constructor(rawLocation, type, time, locationPool) {
this._type = type;
this._time = time; this._time = time;
this._uiLocation = null;
Bindings.debuggerWorkspaceBinding.createLiveLocation(rawLocation, this.updateLocation.bind(this), locationPool); Bindings.debuggerWorkspaceBinding.createLiveLocation(rawLocation, this.updateLocation.bind(this), locationPool);
} }
...@@ -157,42 +215,55 @@ PerfUI.LineLevelProfile.Presentation = class { ...@@ -157,42 +215,55 @@ PerfUI.LineLevelProfile.Presentation = class {
*/ */
updateLocation(liveLocation) { updateLocation(liveLocation) {
if (this._uiLocation) if (this._uiLocation)
this._uiLocation.uiSourceCode.removeDecorationsForType(PerfUI.LineLevelProfile.LineDecorator.type); this._uiLocation.uiSourceCode.removeDecorationsForType(this._type);
this._uiLocation = liveLocation.uiLocation(); this._uiLocation = liveLocation.uiLocation();
if (this._uiLocation) { if (this._uiLocation)
this._uiLocation.uiSourceCode.addLineDecoration( this._uiLocation.uiSourceCode.addLineDecoration(this._uiLocation.lineNumber, this._type, this._time);
this._uiLocation.lineNumber, PerfUI.LineLevelProfile.LineDecorator.type, this._time);
}
} }
}; };
/** /**
* @implements {SourceFrame.LineDecorator} * @implements {SourceFrame.LineDecorator}
* @unrestricted
*/ */
PerfUI.LineLevelProfile.LineDecorator = class { PerfUI.LineLevelProfile.LineDecorator = class {
/** /**
* @override * @override
* @param {!Workspace.UISourceCode} uiSourceCode * @param {!Workspace.UISourceCode} uiSourceCode
* @param {!TextEditor.CodeMirrorTextEditor} textEditor * @param {!TextEditor.CodeMirrorTextEditor} textEditor
* @param {string} type
*/ */
decorate(uiSourceCode, textEditor) { decorate(uiSourceCode, textEditor, type) {
const gutterType = 'CodeMirror-gutter-performance'; const gutterType = `CodeMirror-gutter-${type}`;
const decorations = uiSourceCode.decorationsForType(PerfUI.LineLevelProfile.LineDecorator.type); const decorations = uiSourceCode.decorationsForType(type);
textEditor.uninstallGutter(gutterType); textEditor.uninstallGutter(gutterType);
if (!decorations || !decorations.size) if (!decorations || !decorations.size)
return; return;
textEditor.installGutter(gutterType, false); textEditor.installGutter(gutterType, false);
for (const decoration of decorations) { for (const decoration of decorations) {
const time = /** @type {number} */ (decoration.data()); const value = /** @type {number} */ (decoration.data());
const text = Common.UIString('%.1f\xa0ms', time); const element = this._createElement(type, value);
const intensity = Number.constrain(Math.log10(1 + 2 * time) / 5, 0.02, 1);
const element = createElementWithClass('div', 'text-editor-line-marker-performance');
element.textContent = text;
element.style.backgroundColor = `hsla(44, 100%, 50%, ${intensity.toFixed(3)})`;
textEditor.setGutterDecoration(decoration.range().startLine, gutterType, element); textEditor.setGutterDecoration(decoration.range().startLine, gutterType, element);
} }
} }
};
PerfUI.LineLevelProfile.LineDecorator.type = 'performance'; /**
* @param {string} type
* @param {number} value
* @return {!Element}
*/
_createElement(type, value) {
const element = createElementWithClass('div', 'text-editor-line-marker-text');
if (type === 'performance') {
const intensity = Number.constrain(Math.log10(1 + 2 * value) / 5, 0.02, 1);
element.textContent = Common.UIString('%.1f', value);
element.style.backgroundColor = `hsla(44, 100%, 50%, ${intensity.toFixed(3)})`;
element.createChild('span', 'line-marker-units').textContent = ls`ms`;
} else {
const intensity = Number.constrain(Math.log10(1 + 2e-3 * value) / 5, 0.02, 1);
element.textContent = Common.UIString('%.0f', value / 1024);
element.style.backgroundColor = `hsla(217, 100%, 70%, ${intensity.toFixed(3)})`;
element.createChild('span', 'line-marker-units').textContent = ls`KB`;
}
return element;
}
};
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
"className": "PerfUI.LineLevelProfile.LineDecorator", "className": "PerfUI.LineLevelProfile.LineDecorator",
"decoratorType": "performance" "decoratorType": "performance"
}, },
{
"type": "@SourceFrame.LineDecorator",
"className": "PerfUI.LineLevelProfile.LineDecorator",
"decoratorType": "memory"
},
{ {
"type": "setting", "type": "setting",
"category": "Performance", "category": "Performance",
......
...@@ -46,7 +46,7 @@ Profiler.CPUProfileView = class extends Profiler.ProfileView { ...@@ -46,7 +46,7 @@ Profiler.CPUProfileView = class extends Profiler.ProfileView {
*/ */
wasShown() { wasShown() {
super.wasShown(); super.wasShown();
const lineLevelProfile = PerfUI.LineLevelProfile.instance(); const lineLevelProfile = PerfUI.LineLevelProfile.Performance.instance();
lineLevelProfile.reset(); lineLevelProfile.reset();
lineLevelProfile.appendCPUProfile(this._profileHeader.profileModel()); lineLevelProfile.appendCPUProfile(this._profileHeader.profileModel());
} }
......
...@@ -36,9 +36,12 @@ SDK.HeapProfilerModel = class extends SDK.SDKModel { ...@@ -36,9 +36,12 @@ SDK.HeapProfilerModel = class extends SDK.SDKModel {
this._heapProfilerAgent.enable(); this._heapProfilerAgent.enable();
} }
startSampling() { /**
* @param {number=} samplingRateInBytes
*/
startSampling(samplingRateInBytes) {
const defaultSamplingIntervalInBytes = 16384; const defaultSamplingIntervalInBytes = 16384;
this._heapProfilerAgent.startSampling(defaultSamplingIntervalInBytes); this._heapProfilerAgent.startSampling(samplingRateInBytes || defaultSamplingIntervalInBytes);
} }
/** /**
......
...@@ -757,8 +757,9 @@ SourceFrame.LineDecorator.prototype = { ...@@ -757,8 +757,9 @@ SourceFrame.LineDecorator.prototype = {
/** /**
* @param {!Workspace.UISourceCode} uiSourceCode * @param {!Workspace.UISourceCode} uiSourceCode
* @param {!TextEditor.CodeMirrorTextEditor} textEditor * @param {!TextEditor.CodeMirrorTextEditor} textEditor
* @param {string} type
*/ */
decorate(uiSourceCode, textEditor) {} decorate(uiSourceCode, textEditor, type) {}
}; };
/** /**
......
...@@ -499,20 +499,18 @@ Sources.UISourceCodeFrame = class extends SourceFrame.SourceFrame { ...@@ -499,20 +499,18 @@ Sources.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
/** /**
* @param {string} type * @param {string} type
*/ */
_decorateTypeThrottled(type) { async _decorateTypeThrottled(type) {
if (this._typeDecorationsPending.has(type)) if (this._typeDecorationsPending.has(type))
return; return;
this._typeDecorationsPending.add(type); this._typeDecorationsPending.add(type);
self.runtime.extensions(SourceFrame.LineDecorator) const decorator = await self.runtime.extensions(SourceFrame.LineDecorator)
.find(extension => extension.descriptor()['decoratorType'] === type) .find(extension => extension.descriptor()['decoratorType'] === type)
.instance() .instance();
.then(decorator => { this._typeDecorationsPending.delete(type);
this._typeDecorationsPending.delete(type); this.textEditor.codeMirror().operation(() => {
this.textEditor.codeMirror().operation(() => { decorator.decorate(
decorator.decorate( this._persistenceBinding ? this._persistenceBinding.network : this.uiSourceCode(), this.textEditor, type);
this._persistenceBinding ? this._persistenceBinding.network : this.uiSourceCode(), this.textEditor); });
});
});
} }
_decorateAllTypes() { _decorateAllTypes() {
......
...@@ -10,7 +10,13 @@ ...@@ -10,7 +10,13 @@
} }
.CodeMirror-gutter-performance { .CodeMirror-gutter-performance {
width: 74px; width: 60px;
background-color: white;
margin-left: 3px;
}
.CodeMirror-gutter-memory {
width: 60px;
background-color: white; background-color: white;
margin-left: 3px; margin-left: 3px;
} }
...@@ -425,11 +431,16 @@ div.CodeMirror:focus-within span.CodeMirror-nonmatchingbracket { ...@@ -425,11 +431,16 @@ div.CodeMirror:focus-within span.CodeMirror-nonmatchingbracket {
color: #eee; color: #eee;
} }
.CodeMirror .text-editor-line-marker-performance { .CodeMirror .text-editor-line-marker-text {
text-align: right; text-align: right;
padding-right: 3px; padding-right: 3px;
} }
.CodeMirror .text-editor-line-marker-text span.line-marker-units {
color: #999;
margin-left: 3px;
}
.CodeMirror .text-editor-coverage-unused-marker { .CodeMirror .text-editor-coverage-unused-marker {
text-align: right; text-align: right;
padding-right: 2px; padding-right: 2px;
......
...@@ -549,7 +549,7 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -549,7 +549,7 @@ Timeline.TimelinePanel = class extends UI.Panel {
} }
_reset() { _reset() {
PerfUI.LineLevelProfile.instance().reset(); PerfUI.LineLevelProfile.Performance.instance().reset();
this._setModel(null); this._setModel(null);
} }
...@@ -582,8 +582,10 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -582,8 +582,10 @@ Timeline.TimelinePanel = class extends UI.Panel {
Timeline.PerformanceModel.Events.WindowChanged, this._onModelWindowChanged, this); Timeline.PerformanceModel.Events.WindowChanged, this._onModelWindowChanged, this);
this._overviewPane.setBounds( this._overviewPane.setBounds(
model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime()); model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime());
const lineLevelProfile = PerfUI.LineLevelProfile.Performance.instance();
lineLevelProfile.reset();
for (const profile of model.timelineModel().cpuProfiles()) for (const profile of model.timelineModel().cpuProfiles())
PerfUI.LineLevelProfile.instance().appendCPUProfile(profile); lineLevelProfile.appendCPUProfile(profile);
this._setMarkers(model.timelineModel()); this._setMarkers(model.timelineModel());
this._flameChart.setSelection(null); this._flameChart.setSelection(null);
this._overviewPane.setWindowTimes(model.window().left, model.window().right); this._overviewPane.setWindowTimes(model.window().left, model.window().right);
......
Tests that a line-level CPU profile is shown in the text editor. Tests that a line-level CPU profile is shown in the text editor.
.../devtools/tracing/resources/empty.js .../devtools/tracing/resources/empty.js
99 CodeMirror-gutter-performance 10.0 ms rgba(255, 187, 0, 0.263) 99 CodeMirror-gutter-performance 10.0ms rgba(255, 187, 0, 0.263)
101 CodeMirror-gutter-performance 1900.0 ms rgba(255, 187, 0, 0.718) 101 CodeMirror-gutter-performance 1900.0ms rgba(255, 187, 0, 0.718)
0 CodeMirror-gutter-performance 100.0 ms rgba(255, 187, 0, 0.463) 0 CodeMirror-gutter-performance 100.0ms rgba(255, 187, 0, 0.463)
1 CodeMirror-gutter-performance 200.0 ms rgba(255, 187, 0, 0.52) 1 CodeMirror-gutter-performance 200.0ms rgba(255, 187, 0, 0.52)
2 CodeMirror-gutter-performance 300.0 ms rgba(255, 187, 0, 0.557) 2 CodeMirror-gutter-performance 300.0ms rgba(255, 187, 0, 0.557)
3 CodeMirror-gutter-performance 400.0 ms rgba(255, 187, 0, 0.58) 3 CodeMirror-gutter-performance 400.0ms rgba(255, 187, 0, 0.58)
54 CodeMirror-gutter-performance 220.0 ms rgba(255, 187, 0, 0.53) 54 CodeMirror-gutter-performance 220.0ms rgba(255, 187, 0, 0.53)
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
var url = frame.uiSourceCode().url(); var url = frame.uiSourceCode().url();
TestRunner.addResult(TestRunner.formatters.formatAsURL(url)); TestRunner.addResult(TestRunner.formatters.formatAsURL(url));
cpuProfile.nodes.forEach(n => n.callFrame.url = url); cpuProfile.nodes.forEach(n => n.callFrame.url = url);
var lineProfile = PerfUI.LineLevelProfile.instance(); var lineProfile = PerfUI.LineLevelProfile.Performance.instance();
lineProfile.appendCPUProfile(new SDK.CPUProfileDataModel(cpuProfile)); lineProfile.appendCPUProfile(new SDK.CPUProfileDataModel(cpuProfile));
setTimeout(() => TestRunner.completeTest(), 0); setTimeout(() => TestRunner.completeTest(), 0);
} }
......
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