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. // Copyright 2016 The Chromium Authors. All rights reserved.
// 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.
/**
* @unrestricted
*/
PerfUI.LineLevelProfile = class { PerfUI.LineLevelProfile = class {
constructor() { constructor() {
this._locationPool = new Bindings.LiveLocationPool(); this._locationPool = new Bindings.LiveLocationPool();
this._updateTimer = null;
this.reset(); 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} * @return {!PerfUI.LineLevelProfile}
*/ */
...@@ -23,6 +29,12 @@ PerfUI.LineLevelProfile = class { ...@@ -23,6 +29,12 @@ PerfUI.LineLevelProfile = class {
* @param {!SDK.CPUProfileDataModel} profile * @param {!SDK.CPUProfileDataModel} profile
*/ */
_appendLegacyCPUProfile(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 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) {
...@@ -32,10 +44,10 @@ PerfUI.LineLevelProfile = class { ...@@ -32,10 +44,10 @@ PerfUI.LineLevelProfile = class {
nodesToGo.push(node); nodesToGo.push(node);
if (!node.url || !node.positionTicks) if (!node.url || !node.positionTicks)
continue; continue;
let fileInfo = this._files.get(node.url); let fileInfo = dataByTarget.get(node.url);
if (!fileInfo) { if (!fileInfo) {
fileInfo = new Map(); fileInfo = new Map();
this._files.set(node.url, fileInfo); 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];
...@@ -53,32 +65,34 @@ PerfUI.LineLevelProfile = class { ...@@ -53,32 +65,34 @@ PerfUI.LineLevelProfile = class {
appendCPUProfile(profile) { appendCPUProfile(profile) {
if (!profile.lines) { if (!profile.lines) {
this._appendLegacyCPUProfile(profile); this._appendLegacyCPUProfile(profile);
} else { this._scheduleUpdate();
for (let i = 1; i < profile.samples.length; ++i) { return;
const line = profile.lines[i]; }
if (!line) const target = profile.target();
continue; let dataByTarget = this._lineData.get(target);
const node = profile.nodeByIndex(i); if (!dataByTarget) {
if (!node.url) dataByTarget = new Map();
continue; this._lineData.set(target, dataByTarget);
let fileInfo = this._files.get(node.url); }
if (!fileInfo) { for (let i = 1; i < profile.samples.length; ++i) {
fileInfo = new Map(); const line = profile.lines[i];
this._files.set(node.url, fileInfo); if (!line)
} continue;
const time = profile.timestamps[i] - profile.timestamps[i - 1]; const node = profile.nodeByIndex(i);
fileInfo.set(line, (fileInfo.get(line) || 0) + time); 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(); this._scheduleUpdate();
} }
reset() {
/** @type {!Map<string, !Map<number, number>>} */
this._files = new Map();
this._scheduleUpdate();
}
_scheduleUpdate() { _scheduleUpdate() {
if (this._updateTimer) if (this._updateTimer)
return; return;
...@@ -89,33 +103,41 @@ PerfUI.LineLevelProfile = class { ...@@ -89,33 +103,41 @@ PerfUI.LineLevelProfile = class {
} }
_doUpdate() { _doUpdate() {
// TODO(alph): use scriptId instead of urls for the target.
this._locationPool.disposeAll(); this._locationPool.disposeAll();
Workspace.workspace.uiSourceCodes().forEach( Workspace.workspace.uiSourceCodes().forEach(
uiSourceCode => uiSourceCode.removeDecorationsForType(PerfUI.LineLevelProfile.LineDecorator.type)); uiSourceCode => uiSourceCode.removeDecorationsForType(PerfUI.LineLevelProfile.LineDecorator.type));
for (const fileInfo of this._files) { for (const targetToScript of this._lineData) {
const url = /** @type {string} */ (fileInfo[0]); const target = /** @type {?SDK.Target} */ (targetToScript[0]);
const uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url);
if (!uiSourceCode)
continue;
const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode) || SDK.targetManager.mainTarget();
const debuggerModel = target ? target.model(SDK.DebuggerModel) : null; const debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
if (!debuggerModel) const scriptToLineMap = /** @type {!Map<string|number, !Map<number, number>>} */ (targetToScript[1]);
continue; for (const scriptToLine of scriptToLineMap) {
for (const lineInfo of fileInfo[1]) { const scriptIdOrUrl = /** @type {string|number} */ (scriptToLine[0]);
const line = lineInfo[0] - 1; const lineToDataMap = /** @type {!Map<number, number>} */ (scriptToLine[1]);
const time = lineInfo[1]; // debuggerModel is null when the profile is loaded from file.
const rawLocation = debuggerModel.createRawLocationByURL(url, line, 0); // Try to get UISourceCode by the URL in this case.
if (rawLocation) const uiSourceCode = !debuggerModel && typeof scriptIdOrUrl === 'string' ?
new PerfUI.LineLevelProfile.Presentation(rawLocation, time, this._locationPool); Workspace.workspace.uiSourceCodeForURL(scriptIdOrUrl) :
else if (uiSourceCode) null;
uiSourceCode.addLineDecoration(line, PerfUI.LineLevelProfile.LineDecorator.type, time); 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 * @unrestricted
*/ */
......
...@@ -242,7 +242,8 @@ Profiler.CPUProfileHeader = class extends Profiler.WritableProfileHeader { ...@@ -242,7 +242,8 @@ Profiler.CPUProfileHeader = class extends Profiler.WritableProfileHeader {
* @param {!Protocol.Profiler.Profile} profile * @param {!Protocol.Profiler.Profile} profile
*/ */
setProfile(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 { ...@@ -33,9 +33,10 @@ SDK.CPUProfileNode = class extends SDK.ProfileNode {
SDK.CPUProfileDataModel = class extends SDK.ProfileTreeModel { SDK.CPUProfileDataModel = class extends SDK.ProfileTreeModel {
/** /**
* @param {!Protocol.Profiler.Profile} profile * @param {!Protocol.Profiler.Profile} profile
* @param {?SDK.Target} target
*/ */
constructor(profile) { constructor(profile, target) {
super(); super(target);
const isLegacyFormat = !!profile['head']; const isLegacyFormat = !!profile['head'];
if (isLegacyFormat) { if (isLegacyFormat) {
// Legacy format contains raw timestamps and start/stop times are in seconds. // Legacy format contains raw timestamps and start/stop times are in seconds.
......
...@@ -65,6 +65,13 @@ SDK.ProfileNode = class { ...@@ -65,6 +65,13 @@ SDK.ProfileNode = class {
* @unrestricted * @unrestricted
*/ */
SDK.ProfileTreeModel = class { SDK.ProfileTreeModel = class {
/**
* @param {?SDK.Target=} target
*/
constructor(target) {
this._target = target || null;
}
/** /**
* @param {!SDK.ProfileNode} root * @param {!SDK.ProfileNode} root
* @protected * @protected
...@@ -117,4 +124,11 @@ SDK.ProfileTreeModel = class { ...@@ -117,4 +124,11 @@ SDK.ProfileTreeModel = class {
} }
return root.total; return root.total;
} }
/**
* @return {?SDK.Target}
*/
target() {
return this._target;
}
}; };
...@@ -420,18 +420,21 @@ TimelineModel.TimelineModel = class { ...@@ -420,18 +420,21 @@ TimelineModel.TimelineModel = class {
_extractCpuProfile(tracingModel, thread) { _extractCpuProfile(tracingModel, thread) {
const events = thread.events(); const events = thread.events();
let cpuProfile; let cpuProfile;
let target = null;
// Check for legacy CpuProfile event format first. // Check for legacy CpuProfile event format first.
let cpuProfileEvent = events.peekLast(); let cpuProfileEvent = events.peekLast();
if (cpuProfileEvent && cpuProfileEvent.name === TimelineModel.TimelineModel.RecordType.CpuProfile) { if (cpuProfileEvent && cpuProfileEvent.name === TimelineModel.TimelineModel.RecordType.CpuProfile) {
const eventData = cpuProfileEvent.args['data']; const eventData = cpuProfileEvent.args['data'];
cpuProfile = /** @type {?Protocol.Profiler.Profile} */ (eventData && eventData['cpuProfile']); cpuProfile = /** @type {?Protocol.Profiler.Profile} */ (eventData && eventData['cpuProfile']);
target = this.targetByEvent(cpuProfileEvent);
} }
if (!cpuProfile) { if (!cpuProfile) {
cpuProfileEvent = events.find(e => e.name === TimelineModel.TimelineModel.RecordType.Profile); cpuProfileEvent = events.find(e => e.name === TimelineModel.TimelineModel.RecordType.Profile);
if (!cpuProfileEvent) if (!cpuProfileEvent)
return null; return null;
target = this.targetByEvent(cpuProfileEvent);
const profileGroup = tracingModel.profileGroup(cpuProfileEvent); const profileGroup = tracingModel.profileGroup(cpuProfileEvent);
if (!profileGroup) { if (!profileGroup) {
Common.console.error('Invalid CPU profile format.'); Common.console.error('Invalid CPU profile format.');
...@@ -468,7 +471,7 @@ TimelineModel.TimelineModel = class { ...@@ -468,7 +471,7 @@ TimelineModel.TimelineModel = class {
} }
try { try {
const jsProfileModel = new SDK.CPUProfileDataModel(cpuProfile); const jsProfileModel = new SDK.CPUProfileDataModel(cpuProfile, target);
this._cpuProfiles.push(jsProfileModel); this._cpuProfiles.push(jsProfileModel);
return jsProfileModel; return jsProfileModel;
} catch (e) { } 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