From a4ae4466c15e59165439b749830dd6939d2f6bb9 Mon Sep 17 00:00:00 2001 From: alph Date: Sat, 12 Mar 2016 02:06:45 -0800 Subject: [PATCH] DevTools: Initial implementation of line-level CPU profile. The JS CPU profile is available when there's a recorded timeline with a JS profile. It is put behind an experiment. Things to do: - support source maps. - make it possible to hide profile without reseting timeline. BUG=590936 Review URL: https://codereview.chromium.org/1748993002 Cr-Commit-Position: refs/heads/master@{#380873} --- front_end/sdk/CPUProfileDataModel.js | 1 + front_end/source_frame/cmdevtools.css | 91 ++++++++++++++----------- front_end/timeline/TimelineJSProfile.js | 7 +- front_end/timeline/TimelineModel.js | 64 ++++++++++++++++- front_end/timeline/TimelinePanel.js | 24 +++++++ front_end/timeline/TimelineUIUtils.js | 33 +++++++++ front_end/timeline/module.json | 7 ++ 7 files changed, 180 insertions(+), 47 deletions(-) diff --git a/front_end/sdk/CPUProfileDataModel.js b/front_end/sdk/CPUProfileDataModel.js index 8a0a92c50b..d51f72d510 100644 --- a/front_end/sdk/CPUProfileDataModel.js +++ b/front_end/sdk/CPUProfileDataModel.js @@ -40,6 +40,7 @@ WebInspector.CPUProfileDataModel.prototype = { return result; } profile.totalHitCount = totalHitCount(profile.head); + this.totalHitCount = profile.totalHitCount; var duration = this.profileEndTime - this.profileStartTime; var samplingInterval = duration / profile.totalHitCount; diff --git a/front_end/source_frame/cmdevtools.css b/front_end/source_frame/cmdevtools.css index 87887a7851..a03ddad525 100644 --- a/front_end/source_frame/cmdevtools.css +++ b/front_end/source_frame/cmdevtools.css @@ -1,28 +1,34 @@ .CodeMirror { - line-height: 1.2em !important; - background-color: transparent !important; + line-height: 1.2em !important; + background-color: transparent !important; } .CodeMirror-linewidget { - overflow: visible !important; + overflow: visible !important; +} + +.CodeMirror-gutter-performance { + width: 74px; + background-color: white; + margin-left: 3px; } .CodeMirror .source-frame-eval-expression { - outline: 0; - border: 1px solid rgb(163, 41, 34); - border-left-width: 0; - border-right-width: 0; - background-color: rgb(255, 255, 194); + outline: 0; + border: 1px solid rgb(163, 41, 34); + border-left-width: 0; + border-right-width: 0; + background-color: rgb(255, 255, 194); } .CodeMirror .source-frame-eval-expression-end { - border-right-width: 1px; - margin-right: -1px; + border-right-width: 1px; + margin-right: -1px; } .CodeMirror .source-frame-eval-expression-start { - border-left-width: 1px; - margin-left: -1px; + border-left-width: 1px; + margin-left: -1px; } .CodeMirror-readonly .CodeMirror-cursor { @@ -30,23 +36,23 @@ } .CodeMirror .CodeMirror-gutters { - border-right: 1px solid rgb(187, 187, 187); - background-color: #eee; + border-right: 1px solid rgb(187, 187, 187); + background-color: #eee; } .CodeMirror .CodeMirror-linenumber { - color: rgb(128, 128, 128); + color: rgb(128, 128, 128); } .CodeMirror-linenumber { - min-width: 22px !important; + min-width: 22px !important; } .cm-highlight { - -webkit-animation: fadeout 2s 0s; + -webkit-animation: fadeout 2s 0s; } .-theme-with-dark-background .cm-highlight { - -webkit-animation: fadeout-dark 2s 0s; + -webkit-animation: fadeout-dark 2s 0s; } @-webkit-keyframes fadeout { from {background-color: rgb(255, 255, 120); } @@ -58,7 +64,7 @@ } .cm-highlight.cm-execution-line { - -webkit-animation: fadeout-execution-line 1s 0s; + -webkit-animation: fadeout-execution-line 1s 0s; } @-webkit-keyframes fadeout-execution-line { from {background-color: rgb(121, 141, 254); } @@ -66,35 +72,35 @@ } .cm-breakpoint .CodeMirror-linenumber { - color: white; - border-width: 1px 4px 1px 1px !important; - -webkit-border-image: url(Images/breakpoint.png) 1 4 1 1; - margin: 0 0 0 3px !important; - padding-right: 3px; - padding-left: 1px; - height: 11px; - line-height: 12px !important; + color: white; + border-width: 1px 4px 1px 1px !important; + -webkit-border-image: url(Images/breakpoint.png) 1 4 1 1; + margin: 0 0 0 3px !important; + padding-right: 3px; + padding-left: 1px; + height: 11px; + line-height: 12px !important; } .cm-line-without-source-mapping { - background-color: #fafafa; + background-color: #fafafa; } .cm-breakpoint.cm-breakpoint-conditional .CodeMirror-linenumber { - -webkit-border-image: url(Images/breakpointConditional.png) 1 4 1 1; + -webkit-border-image: url(Images/breakpointConditional.png) 1 4 1 1; } @media (-webkit-min-device-pixel-ratio: 1.5) { .cm-breakpoint .CodeMirror-linenumber { - -webkit-border-image: url(Images/breakpoint_2x.png) 2 8 2 2; + -webkit-border-image: url(Images/breakpoint_2x.png) 2 8 2 2; } .cm-breakpoint.cm-breakpoint-conditional .CodeMirror-linenumber { - -webkit-border-image: url(Images/breakpointConditional_2x.png) 2 8 2 2; + -webkit-border-image: url(Images/breakpointConditional_2x.png) 2 8 2 2; } } /* media */ .cm-breakpoint-disabled .CodeMirror-linenumber { - opacity: 0.5; + opacity: 0.5; } .breakpoints-deactivated .cm-breakpoint .CodeMirror-linenumber { @@ -106,12 +112,12 @@ } .CodeMirror-matchingbracket { - border-bottom: 1px solid black; - color: #222 !important; + border-bottom: 1px solid black; + color: #222 !important; } .CodeMirror-nonmatchingbracket { - color: #222 !important; + color: #222 !important; } .cm-whitespace::before { @@ -242,6 +248,11 @@ color: #eee; } +.CodeMirror .text-editor-line-marker-performance { + text-align: right; + padding-right: 3px; +} + .CodeMirror .text-editor-line-decoration { position: absolute; } @@ -309,9 +320,9 @@ } .CodeMirror .text-editor-line-decoration-wave { - background-image: url(Images/errorWave.png); - background-repeat: repeat-x; - background-size: contain; + background-image: url(Images/errorWave.png); + background-repeat: repeat-x; + background-size: contain; } @media (-webkit-min-device-pixel-ratio: 1.5) { @@ -322,11 +333,11 @@ /** @see crbug.com/358161 */ .CodeMirror .CodeMirror-vscrollbar, .CodeMirror .CodeMirror-hscrollbar { - transform: translateZ(0); + transform: translateZ(0); } .CodeMirror .CodeMirror-activeline-background { - background-color: transparent; + background-color: transparent; } .cm-trailing-whitespace { diff --git a/front_end/timeline/TimelineJSProfile.js b/front_end/timeline/TimelineJSProfile.js index ea1e23a312..d9e825be28 100644 --- a/front_end/timeline/TimelineJSProfile.js +++ b/front_end/timeline/TimelineJSProfile.js @@ -6,15 +6,12 @@ WebInspector.TimelineJSProfileProcessor = { }; /** - * @param {!ProfilerAgent.CPUProfile} jsProfile + * @param {!WebInspector.CPUProfileDataModel} jsProfileModel * @param {!WebInspector.TracingModel.Thread} thread * @return {!Array} */ -WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile = function(jsProfile, thread) +WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile = function(jsProfileModel, thread) { - if (!jsProfile.samples) - return []; - var jsProfileModel = new WebInspector.CPUProfileDataModel(jsProfile); var idleNode = jsProfileModel.idleNode; var programNode = jsProfileModel.programNode; var gcNode = jsProfileModel.gcNode; diff --git a/front_end/timeline/TimelineModel.js b/front_end/timeline/TimelineModel.js index 3a47aec5c1..f2762eaca7 100644 --- a/front_end/timeline/TimelineModel.js +++ b/front_end/timeline/TimelineModel.js @@ -729,8 +729,11 @@ WebInspector.TimelineModel.prototype = { var cpuProfileEvent = events.peekLast(); if (cpuProfileEvent && cpuProfileEvent.name === WebInspector.TimelineModel.RecordType.CpuProfile) { var cpuProfile = cpuProfileEvent.args["data"]["cpuProfile"]; - if (cpuProfile) - jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(cpuProfile, thread); + if (cpuProfile) { + var jsProfileModel = new WebInspector.CPUProfileDataModel(cpuProfile); + this._lineLevelCPUProfile.appendCPUProfile(jsProfileModel); + jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(jsProfileModel, thread); + } } } @@ -1061,6 +1064,7 @@ WebInspector.TimelineModel.prototype = { reset: function() { + this._lineLevelCPUProfile = new WebInspector.TimelineModel.LineLevelProfile(); this._virtualThreads = []; /** @type {!Array.} */ this._mainThreadEvents = []; @@ -1084,6 +1088,14 @@ WebInspector.TimelineModel.prototype = { this._maximumRecordTime = 0; }, + /** + * @return {!WebInspector.TimelineModel.LineLevelProfile} + */ + lineLevelCPUProfile: function() + { + return this._lineLevelCPUProfile; + }, + /** * @return {number} */ @@ -1723,3 +1735,51 @@ WebInspector.TimelineAsyncEventTracker.prototype = { event.initiator = initiatorMap.get(id) || null; } } + +/** + * @constructor + */ +WebInspector.TimelineModel.LineLevelProfile = function() +{ + /** @type {!Map>} */ + this._files = new Map(); +} + +WebInspector.TimelineModel.LineLevelProfile.prototype = { + /** + * @param {!WebInspector.CPUProfileDataModel} profile + */ + appendCPUProfile: function(profile) + { + var nodesToGo = [profile.profileHead]; + var sampleDuration = (profile.profileEndTime - profile.profileStartTime) / profile.totalHitCount; + while (nodesToGo.length) { + var nodes = nodesToGo.pop().children; + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; + nodesToGo.push(node); + if (!node.url || !node.positionTicks) + continue; + var fileInfo = this._files.get(node.url); + if (!fileInfo) { + fileInfo = new Map(); + this._files.set(node.url, fileInfo); + } + for (var j = 0; j < node.positionTicks.length; ++j) { + var lineInfo = node.positionTicks[j]; + var line = lineInfo.line - 1; + var time = lineInfo.ticks * sampleDuration; + fileInfo.set(line, (fileInfo.get(line) || 0) + time); + } + } + } + }, + + /** + * @return {!Map>} + */ + files: function() + { + return this._files; + } +} \ No newline at end of file diff --git a/front_end/timeline/TimelinePanel.js b/front_end/timeline/TimelinePanel.js index dd62942a1e..d7082c7a65 100644 --- a/front_end/timeline/TimelinePanel.js +++ b/front_end/timeline/TimelinePanel.js @@ -679,6 +679,7 @@ WebInspector.TimelinePanel.prototype = { { this._tracingModel.reset(); this._model.reset(); + this._resetLineLevelCPUProfile(); this._showRecordingHelpMessage(); this.requestWindowTimes(0, Infinity); @@ -806,6 +807,7 @@ WebInspector.TimelinePanel.prototype = { this._frameModel.addTraceEvents(this._model.target(), this._model.inspectedTargetEvents(), this._model.sessionId() || ""); if (this._irModel) this._irModel.populate(this._model); + this._setLineLevelCPUProfile(this._model.lineLevelCPUProfile()); if (this._statusPane) this._statusPane.hide(); delete this._statusPane; @@ -1261,6 +1263,28 @@ WebInspector.TimelinePanel.prototype = { this.requestWindowTimes(leftTime, rightTime); }, + /** + * @param {!WebInspector.TimelineModel.LineLevelProfile} profile + */ + _setLineLevelCPUProfile: function(profile) + { + for (var fileInfo of profile.files()) { + var uiSourceCode = WebInspector.workspace.uiSourceCodeForURL(/** @type {string} */ (fileInfo[0])); + if (!uiSourceCode) + continue; + for (var lineInfo of fileInfo[1]) { + var line = lineInfo[0]; + var time = lineInfo[1]; + uiSourceCode.addLineDecoration(line, WebInspector.TimelineUIUtils.PerformanceLineDecorator.type, time); + } + } + }, + + _resetLineLevelCPUProfile: function() + { + WebInspector.workspace.uiSourceCodes().forEach(uiSourceCode => uiSourceCode.removeAllLineDecorations(WebInspector.TimelineUIUtils.PerformanceLineDecorator.type)); + }, + __proto__: WebInspector.Panel.prototype } diff --git a/front_end/timeline/TimelineUIUtils.js b/front_end/timeline/TimelineUIUtils.js index 7cdc39252c..a69268d31b 100644 --- a/front_end/timeline/TimelineUIUtils.js +++ b/front_end/timeline/TimelineUIUtils.js @@ -2101,3 +2101,36 @@ WebInspector.TimelineUIUtils.eventWarning = function(event, warningType) } return span; } + +/** + * @constructor + * @implements {WebInspector.UISourceCodeFrame.LineDecorator} + */ +WebInspector.TimelineUIUtils.PerformanceLineDecorator = function() +{ +} + +WebInspector.TimelineUIUtils.PerformanceLineDecorator.type = "performance"; + +WebInspector.TimelineUIUtils.PerformanceLineDecorator.prototype = { + /** + * @override + * @param {!WebInspector.UISourceCode} uiSourceCode + * @param {!WebInspector.CodeMirrorTextEditor} textEditor + */ + decorate: function(uiSourceCode, textEditor) + { + var type = WebInspector.TimelineUIUtils.PerformanceLineDecorator.type; + var decorations = uiSourceCode.lineDecorations(type) || []; + textEditor.resetGutterDecorations(type); + for (var decoration of decorations) { + var time = /** @type {number} */ (decoration.data()); + var text = WebInspector.UIString("%.1f\xa0ms", time); + var intensity = Number.constrain(Math.log10(1 + 2 * time) / 5, 0.02, 1); + var element = createElementWithClass("div", "text-editor-line-marker-performance"); + element.textContent = text; + element.style.backgroundColor = `rgba(255, 0, 0, ${intensity.toFixed(3)})`; + textEditor.setGutterDecoration(decoration.line(), decoration.type(), element); + } + } +} \ No newline at end of file diff --git a/front_end/timeline/module.json b/front_end/timeline/module.json index 11d544d15f..95d02032de 100644 --- a/front_end/timeline/module.json +++ b/front_end/timeline/module.json @@ -20,6 +20,11 @@ "name": "loadTimelineFromURL", "className": "WebInspector.LoadTimelineHandler" }, + { + "type": "@WebInspector.UISourceCodeFrame.LineDecorator", + "className": "WebInspector.TimelineUIUtils.PerformanceLineDecorator", + "decoratorType": "performance" + }, { "type": "@WebInspector.ActionDelegate", "actionId": "timeline.toggle-recording", @@ -96,6 +101,8 @@ "dependencies": [ "components", "components_lazy", + "source_frame", + "sources", "ui_lazy" ], "scripts": [