mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-13 01:54:52 +08:00
Change-Id: Ic07bc33cdd12a18395fd28a458daf10a8e344d4a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1553700 Reviewed-by: Pavel Feldman <pfeldman@chromium.org> Commit-Queue: Alexei Filippov <alph@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#647925} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: ec57c898b710ad576edfd7001863b756c88824a1
263 lines
8.4 KiB
JavaScript
263 lines
8.4 KiB
JavaScript
// 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.
|
|
|
|
PerfUI.LineLevelProfile = {};
|
|
|
|
PerfUI.LineLevelProfile.Performance = class {
|
|
constructor() {
|
|
this._helper = new PerfUI.LineLevelProfile._Helper('performance');
|
|
}
|
|
|
|
reset() {
|
|
this._helper.reset();
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CPUProfileDataModel} profile
|
|
*/
|
|
_appendLegacyCPUProfile(profile) {
|
|
const target = profile.target();
|
|
const nodesToGo = [profile.profileHead];
|
|
const sampleDuration = (profile.profileEndTime - profile.profileStartTime) / profile.totalHitCount;
|
|
while (nodesToGo.length) {
|
|
const nodes = nodesToGo.pop().children;
|
|
for (let i = 0; i < nodes.length; ++i) {
|
|
const node = nodes[i];
|
|
nodesToGo.push(node);
|
|
if (!node.url || !node.positionTicks)
|
|
continue;
|
|
for (let j = 0; j < node.positionTicks.length; ++j) {
|
|
const lineInfo = node.positionTicks[j];
|
|
const line = lineInfo.line;
|
|
const time = lineInfo.ticks * sampleDuration;
|
|
this._helper.addLineData(target, node.url, line, time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CPUProfileDataModel} profile
|
|
*/
|
|
appendCPUProfile(profile) {
|
|
if (!profile.lines) {
|
|
this._appendLegacyCPUProfile(profile);
|
|
this._helper.scheduleUpdate();
|
|
return;
|
|
}
|
|
const target = profile.target();
|
|
for (let i = 1; i < profile.samples.length; ++i) {
|
|
const line = profile.lines[i];
|
|
if (!line)
|
|
continue;
|
|
const node = profile.nodeByIndex(i);
|
|
const scriptIdOrUrl = node.scriptId || node.url;
|
|
if (!scriptIdOrUrl)
|
|
continue;
|
|
const time = profile.timestamps[i] - profile.timestamps[i - 1];
|
|
this._helper.addLineData(target, scriptIdOrUrl, line, time);
|
|
}
|
|
this._helper.scheduleUpdate();
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
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)
|
|
return;
|
|
this._updateTimer = setTimeout(() => {
|
|
this._updateTimer = null;
|
|
this._doUpdate();
|
|
}, 0);
|
|
}
|
|
|
|
_doUpdate() {
|
|
this._locationPool.disposeAll();
|
|
Workspace.workspace.uiSourceCodes().forEach(uiSourceCode => uiSourceCode.removeDecorationsForType(this._type));
|
|
for (const targetToScript of this._lineData) {
|
|
const target = /** @type {?SDK.Target} */ (targetToScript[0]);
|
|
const debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
|
|
const scriptToLineMap = /** @type {!Map<string|number, !Map<number, number>>} */ (targetToScript[1]);
|
|
for (const scriptToLine of scriptToLineMap) {
|
|
const scriptIdOrUrl = /** @type {string|number} */ (scriptToLine[0]);
|
|
const lineToDataMap = /** @type {!Map<number, number>} */ (scriptToLine[1]);
|
|
// debuggerModel is null when the profile is loaded from file.
|
|
// Try to get UISourceCode by the URL in this case.
|
|
const uiSourceCode = !debuggerModel && typeof scriptIdOrUrl === 'string' ?
|
|
Workspace.workspace.uiSourceCodeForURL(scriptIdOrUrl) :
|
|
null;
|
|
if (!debuggerModel && !uiSourceCode)
|
|
continue;
|
|
for (const lineToData of lineToDataMap) {
|
|
const line = /** @type {number} */ (lineToData[0]) - 1;
|
|
const data = /** @type {number} */ (lineToData[1]);
|
|
if (uiSourceCode) {
|
|
uiSourceCode.addLineDecoration(line, this._type, data);
|
|
continue;
|
|
}
|
|
const rawLocation = typeof scriptIdOrUrl === 'string' ?
|
|
debuggerModel.createRawLocationByURL(scriptIdOrUrl, line, 0) :
|
|
debuggerModel.createRawLocationByScriptId(String(scriptIdOrUrl), line, 0);
|
|
if (rawLocation)
|
|
new PerfUI.LineLevelProfile.Presentation(rawLocation, this._type, data, this._locationPool);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
PerfUI.LineLevelProfile.Presentation = class {
|
|
/**
|
|
* @param {!SDK.DebuggerModel.Location} rawLocation
|
|
* @param {string} type
|
|
* @param {number} time
|
|
* @param {!Bindings.LiveLocationPool} locationPool
|
|
*/
|
|
constructor(rawLocation, type, time, locationPool) {
|
|
this._type = type;
|
|
this._time = time;
|
|
this._uiLocation = null;
|
|
Bindings.debuggerWorkspaceBinding.createLiveLocation(rawLocation, this.updateLocation.bind(this), locationPool);
|
|
}
|
|
|
|
/**
|
|
* @param {!Bindings.LiveLocation} liveLocation
|
|
*/
|
|
updateLocation(liveLocation) {
|
|
if (this._uiLocation)
|
|
this._uiLocation.uiSourceCode.removeDecorationsForType(this._type);
|
|
this._uiLocation = liveLocation.uiLocation();
|
|
if (this._uiLocation)
|
|
this._uiLocation.uiSourceCode.addLineDecoration(this._uiLocation.lineNumber, this._type, this._time);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements {SourceFrame.LineDecorator}
|
|
*/
|
|
PerfUI.LineLevelProfile.LineDecorator = class {
|
|
/**
|
|
* @override
|
|
* @param {!Workspace.UISourceCode} uiSourceCode
|
|
* @param {!TextEditor.CodeMirrorTextEditor} textEditor
|
|
* @param {string} type
|
|
*/
|
|
decorate(uiSourceCode, textEditor, type) {
|
|
const gutterType = `CodeMirror-gutter-${type}`;
|
|
const decorations = uiSourceCode.decorationsForType(type);
|
|
textEditor.uninstallGutter(gutterType);
|
|
if (!decorations || !decorations.size)
|
|
return;
|
|
textEditor.installGutter(gutterType, false);
|
|
for (const decoration of decorations) {
|
|
const value = /** @type {number} */ (decoration.data());
|
|
const element = this._createElement(type, value);
|
|
textEditor.setGutterDecoration(decoration.range().startLine, gutterType, element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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 + 10 * 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.style.backgroundColor = `hsla(217, 100%, 70%, ${intensity.toFixed(3)})`;
|
|
value /= 1e3;
|
|
let units;
|
|
let fractionDigits;
|
|
if (value >= 1e3) {
|
|
units = ls`MB`;
|
|
value /= 1e3;
|
|
fractionDigits = value >= 20 ? 0 : 1;
|
|
} else {
|
|
units = ls`KB`;
|
|
fractionDigits = 0;
|
|
}
|
|
element.textContent = Common.UIString(`%.${fractionDigits}f`, value);
|
|
element.createChild('span', 'line-marker-units').textContent = units;
|
|
}
|
|
return element;
|
|
}
|
|
};
|