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

DevTools: Use scriptId to identify the scripts in line level profile.

Change-Id: I9db43e0082628297ac8320c6eb34a763c8bb0996
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1497302
Commit-Queue: Alexei Filippov <alph@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#637868}
parent 687466cb
// 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.
/**
* @unrestricted
*/
PerfUI.LineLevelProfile = class {
constructor() {
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();
}
/**
* @return {!PerfUI.LineLevelProfile}
*/
......@@ -23,6 +29,12 @@ PerfUI.LineLevelProfile = class {
* @param {!SDK.CPUProfileDataModel} profile
*/
_appendLegacyCPUProfile(profile) {
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 sampleDuration = (profile.profileEndTime - profile.profileStartTime) / profile.totalHitCount;
while (nodesToGo.length) {
......@@ -32,10 +44,10 @@ PerfUI.LineLevelProfile = class {
nodesToGo.push(node);
if (!node.url || !node.positionTicks)
continue;
let fileInfo = this._files.get(node.url);
let fileInfo = dataByTarget.get(node.url);
if (!fileInfo) {
fileInfo = new Map();
this._files.set(node.url, fileInfo);
dataByTarget.set(node.url, fileInfo);
}
for (let j = 0; j < node.positionTicks.length; ++j) {
const lineInfo = node.positionTicks[j];
......@@ -53,32 +65,34 @@ PerfUI.LineLevelProfile = class {
appendCPUProfile(profile) {
if (!profile.lines) {
this._appendLegacyCPUProfile(profile);
} else {
for (let i = 1; i < profile.samples.length; ++i) {
const line = profile.lines[i];
if (!line)
continue;
const node = profile.nodeByIndex(i);
if (!node.url)
continue;
let fileInfo = this._files.get(node.url);
if (!fileInfo) {
fileInfo = new Map();
this._files.set(node.url, fileInfo);
}
const time = profile.timestamps[i] - profile.timestamps[i - 1];
fileInfo.set(line, (fileInfo.get(line) || 0) + time);
this._scheduleUpdate();
return;
}
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) {
const line = profile.lines[i];
if (!line)
continue;
const node = profile.nodeByIndex(i);
const scriptIdOrUrl = node.scriptId || node.url;
if (!scriptIdOrUrl)
continue;
let dataByScript = dataByTarget.get(scriptIdOrUrl);
if (!dataByScript) {
dataByScript = new Map();
dataByTarget.set(scriptIdOrUrl, dataByScript);
}
const time = profile.timestamps[i] - profile.timestamps[i - 1];
dataByScript.set(line, (dataByScript.get(line) || 0) + time);
}
this._scheduleUpdate();
}
reset() {
/** @type {!Map<string, !Map<number, number>>} */
this._files = new Map();
this._scheduleUpdate();
}
_scheduleUpdate() {
if (this._updateTimer)
return;
......@@ -89,33 +103,41 @@ PerfUI.LineLevelProfile = class {
}
_doUpdate() {
// TODO(alph): use scriptId instead of urls for the target.
this._locationPool.disposeAll();
Workspace.workspace.uiSourceCodes().forEach(
uiSourceCode => uiSourceCode.removeDecorationsForType(PerfUI.LineLevelProfile.LineDecorator.type));
for (const fileInfo of this._files) {
const url = /** @type {string} */ (fileInfo[0]);
const uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url);
if (!uiSourceCode)
continue;
const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode) || SDK.targetManager.mainTarget();
for (const targetToScript of this._lineData) {
const target = /** @type {?SDK.Target} */ (targetToScript[0]);
const debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
if (!debuggerModel)
continue;
for (const lineInfo of fileInfo[1]) {
const line = lineInfo[0] - 1;
const time = lineInfo[1];
const rawLocation = debuggerModel.createRawLocationByURL(url, line, 0);
if (rawLocation)
new PerfUI.LineLevelProfile.Presentation(rawLocation, time, this._locationPool);
else if (uiSourceCode)
uiSourceCode.addLineDecoration(line, PerfUI.LineLevelProfile.LineDecorator.type, time);
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, PerfUI.LineLevelProfile.LineDecorator.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, data, this._locationPool);
}
}
}
}
};
/**
* @unrestricted
*/
......
......@@ -242,7 +242,8 @@ Profiler.CPUProfileHeader = class extends Profiler.WritableProfileHeader {
* @param {!Protocol.Profiler.Profile} profile
*/
setProfile(profile) {
this._profileModel = new SDK.CPUProfileDataModel(profile);
const target = this._cpuProfilerModel && this._cpuProfilerModel.target() || null;
this._profileModel = new SDK.CPUProfileDataModel(profile, target);
}
};
......
......@@ -33,9 +33,10 @@ SDK.CPUProfileNode = class extends SDK.ProfileNode {
SDK.CPUProfileDataModel = class extends SDK.ProfileTreeModel {
/**
* @param {!Protocol.Profiler.Profile} profile
* @param {?SDK.Target} target
*/
constructor(profile) {
super();
constructor(profile, target) {
super(target);
const isLegacyFormat = !!profile['head'];
if (isLegacyFormat) {
// Legacy format contains raw timestamps and start/stop times are in seconds.
......
......@@ -65,6 +65,13 @@ SDK.ProfileNode = class {
* @unrestricted
*/
SDK.ProfileTreeModel = class {
/**
* @param {?SDK.Target=} target
*/
constructor(target) {
this._target = target || null;
}
/**
* @param {!SDK.ProfileNode} root
* @protected
......@@ -117,4 +124,11 @@ SDK.ProfileTreeModel = class {
}
return root.total;
}
/**
* @return {?SDK.Target}
*/
target() {
return this._target;
}
};
......@@ -420,18 +420,21 @@ TimelineModel.TimelineModel = class {
_extractCpuProfile(tracingModel, thread) {
const events = thread.events();
let cpuProfile;
let target = null;
// Check for legacy CpuProfile event format first.
let cpuProfileEvent = events.peekLast();
if (cpuProfileEvent && cpuProfileEvent.name === TimelineModel.TimelineModel.RecordType.CpuProfile) {
const eventData = cpuProfileEvent.args['data'];
cpuProfile = /** @type {?Protocol.Profiler.Profile} */ (eventData && eventData['cpuProfile']);
target = this.targetByEvent(cpuProfileEvent);
}
if (!cpuProfile) {
cpuProfileEvent = events.find(e => e.name === TimelineModel.TimelineModel.RecordType.Profile);
if (!cpuProfileEvent)
return null;
target = this.targetByEvent(cpuProfileEvent);
const profileGroup = tracingModel.profileGroup(cpuProfileEvent);
if (!profileGroup) {
Common.console.error('Invalid CPU profile format.');
......@@ -468,7 +471,7 @@ TimelineModel.TimelineModel = class {
}
try {
const jsProfileModel = new SDK.CPUProfileDataModel(cpuProfile);
const jsProfileModel = new SDK.CPUProfileDataModel(cpuProfile, target);
this._cpuProfiles.push(jsProfileModel);
return jsProfileModel;
} catch (e) {
......
Tests that a line-level CPU profile is collected and shown in the text editor.
Decoration found: true
// 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.
(async function() {
TestRunner.addResult(`Tests that a line-level CPU profile is collected and shown in the text editor.\n`);
await TestRunner.loadModule('console_test_runner');
await TestRunner.loadModule('sources_test_runner');
await TestRunner.loadModule('performance_test_runner');
await TestRunner.showPanel('timeline');
await TestRunner.showPanel('sources');
await TestRunner.evaluateInPageAnonymously(`
function performActions() {
console.trace('Message to capture the scriptId');
const endTime = Date.now() + 100;
let s = 0;
while (Date.now() < endTime) s += Math.cos(s);
return s;
}`);
let scriptId;
ConsoleTestRunner.addConsoleSniffer(m => {
if (m.messageText === 'Message to capture the scriptId')
scriptId = m.stackTrace.callFrames[0].scriptId;
}, true);
let hasLineLevelInfo;
do {
await PerformanceTestRunner.evaluateWithTimeline('performActions()');
const events = PerformanceTestRunner.timelineModel().inspectedTargetEvents();
hasLineLevelInfo = events.some(e => e.name === 'ProfileChunk' && e.args.data.lines);
} while (!hasLineLevelInfo);
TestRunner.addSniffer(SourceFrame.SourcesTextEditor.prototype, 'setGutterDecoration', decorationAdded, true);
const debuggerModel = SDK.targetManager.mainTarget().model(SDK.DebuggerModel);
const rawLocation = debuggerModel.createRawLocationByScriptId(scriptId, 0, 0);
const uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation);
await SourcesTestRunner.showUISourceCodePromise(uiLocation.uiSourceCode);
function decorationAdded(line, type, element) {
if (type !== 'CodeMirror-gutter-performance' || line !== 5)
return;
const value = parseFloat(element.textContent);
TestRunner.addResult(`Decoration found: ${isFinite(value)}`);
TestRunner.completeTest();
}
})();
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