diff --git a/lighthouse-core/audits/bootup-time.js b/lighthouse-core/audits/bootup-time.js index de36775af550..130cb5bae8af 100644 --- a/lighthouse-core/audits/bootup-time.js +++ b/lighthouse-core/audits/bootup-time.js @@ -6,9 +6,8 @@ 'use strict'; const Audit = require('./audit'); -const WebInspector = require('../lib/web-inspector'); const Util = require('../report/html/renderer/util'); -const {groupIdToName, taskToGroup} = require('../lib/task-groups'); +const {taskGroups} = require('../lib/task-groups'); class BootupTime extends Audit { /** @@ -41,33 +40,40 @@ class BootupTime extends Audit { } /** - * Returns a mapping of URL to counts of event groups. - * @param {LH.Artifacts.DevtoolsTimelineModel} timelineModel + * @param {LH.WebInspector.NetworkRequest[]} records + */ + static getJavaScriptURLs(records) { + /** @type {Set} */ + const urls = new Set(); + for (const record of records) { + if (record._resourceType && record._resourceType === 'Script') { + urls.add(record.url); + } + } + + return urls; + } + + /** + * @param {LH.Artifacts.TaskNode[]} tasks + * @param {Set} jsURLs * @return {Map>} */ - static getExecutionTimingsByURL(timelineModel) { - const bottomUpByURL = timelineModel.bottomUpGroupBy('URL'); + static getExecutionTimingsByURL(tasks, jsURLs) { /** @type {Map>} */ const result = new Map(); - bottomUpByURL.children.forEach((perUrlNode, url) => { - // when url is "" or about:blank, we skip it - if (!url || url === 'about:blank') { - return; - } + for (const task of tasks) { + const jsURL = task.attributableURLs.find(url => jsURLs.has(url)); + const fallbackURL = task.attributableURLs[0]; + const attributableURL = jsURL || fallbackURL; + if (!attributableURL || attributableURL === 'about:blank') continue; - /** @type {Object} */ - const taskGroups = {}; - perUrlNode.children.forEach((perTaskPerUrlNode) => { - // eventStyle() returns a string like 'Evaluate Script' - const task = WebInspector.TimelineUIUtils.eventStyle(perTaskPerUrlNode.event); - // Resolve which taskGroup we're using - const groupName = taskToGroup[task.title] || groupIdToName.other; - const groupTotal = taskGroups[groupName] || 0; - taskGroups[groupName] = groupTotal + (perTaskPerUrlNode.selfTime || 0); - }); - result.set(url, taskGroups); - }); + const timingByGroupId = result.get(attributableURL) || {}; + const originalTime = timingByGroupId[task.group.id] || 0; + timingByGroupId[task.group.id] = originalTime + task.selfTime; + result.set(attributableURL, timingByGroupId); + } return result; } @@ -80,47 +86,54 @@ class BootupTime extends Audit { static async audit(artifacts, context) { const settings = context.settings || {}; const trace = artifacts.traces[BootupTime.DEFAULT_PASS]; - const devtoolsTimelineModel = await artifacts.requestDevtoolsTimelineModel(trace); - const executionTimings = BootupTime.getExecutionTimingsByURL(devtoolsTimelineModel); - let totalBootupTime = 0; - /** @type {Object>} */ - const extendedInfo = {}; - - const headings = [ - {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'scripting', granularity: 1, itemType: 'ms', text: groupIdToName.scripting}, - {key: 'scriptParseCompile', granularity: 1, itemType: 'ms', - text: groupIdToName.scriptParseCompile}, - ]; - + const devtoolsLog = artifacts.devtoolsLogs[BootupTime.DEFAULT_PASS]; + const networkRecords = await artifacts.requestNetworkRecords(devtoolsLog); + const tasks = await artifacts.requestMainThreadTasks(trace); const multiplier = settings.throttlingMethod === 'simulate' ? settings.throttling.cpuSlowdownMultiplier : 1; - // map data in correct format to create a table + + const jsURLs = BootupTime.getJavaScriptURLs(networkRecords); + const executionTimings = BootupTime.getExecutionTimingsByURL(tasks, jsURLs); + + let totalBootupTime = 0; const results = Array.from(executionTimings) - .map(([url, groups]) => { + .map(([url, timingByGroupId]) => { // Add up the totalBootupTime for all the taskGroups - for (const [name, value] of Object.entries(groups)) { - groups[name] = value * multiplier; - totalBootupTime += value * multiplier; + let bootupTimeForURL = 0; + for (const [groupId, timespanMs] of Object.entries(timingByGroupId)) { + timingByGroupId[groupId] = timespanMs * multiplier; + bootupTimeForURL += timespanMs * multiplier; + } + + // Add up all the execution time of shown URLs + if (bootupTimeForURL >= context.options.thresholdInMs) { + totalBootupTime += bootupTimeForURL; } - extendedInfo[url] = groups; + const scriptingTotal = timingByGroupId[taskGroups.scriptEvaluation.id] || 0; + const parseCompileTotal = timingByGroupId[taskGroups.scriptParseCompile.id] || 0; - const scriptingTotal = groups[groupIdToName.scripting] || 0; - const parseCompileTotal = groups[groupIdToName.scriptParseCompile] || 0; return { url: url, - sum: scriptingTotal + parseCompileTotal, - // Only reveal the javascript task costs - // Later we can account for forced layout costs, etc. + total: bootupTimeForURL, + // Highlight the JavaScript task costs scripting: scriptingTotal, scriptParseCompile: parseCompileTotal, }; }) - .filter(result => result.sum >= context.options.thresholdInMs) - .sort((a, b) => b.sum - a.sum); + .filter(result => result.total >= context.options.thresholdInMs) + .sort((a, b) => b.total - a.total); const summary = {wastedMs: totalBootupTime}; + + const headings = [ + {key: 'url', itemType: 'url', text: 'URL'}, + {key: 'total', granularity: 1, itemType: 'ms', text: 'Total'}, + {key: 'scripting', granularity: 1, itemType: 'ms', text: taskGroups.scriptEvaluation.label}, + {key: 'scriptParseCompile', granularity: 1, itemType: 'ms', + text: taskGroups.scriptParseCompile.label}, + ]; + const details = BootupTime.makeTableDetails(headings, results, summary); const score = Audit.computeLogNormalScore( @@ -134,9 +147,6 @@ class BootupTime extends Audit { rawValue: totalBootupTime, displayValue: [Util.MS_DISPLAY_VALUE, totalBootupTime], details, - extendedInfo: { - value: extendedInfo, - }, }; } } diff --git a/lighthouse-core/audits/mainthread-work-breakdown.js b/lighthouse-core/audits/mainthread-work-breakdown.js index 1e905cedd1dd..b9503f2dd648 100644 --- a/lighthouse-core/audits/mainthread-work-breakdown.js +++ b/lighthouse-core/audits/mainthread-work-breakdown.js @@ -12,8 +12,7 @@ const Audit = require('./audit'); const Util = require('../report/html/renderer/util'); -// We group all trace events into groups to show a highlevel breakdown of the page -const {taskToGroup} = require('../lib/task-groups'); +const {taskGroups} = require('../lib/task-groups'); class MainThreadWorkBreakdown extends Audit { /** @@ -43,15 +42,17 @@ class MainThreadWorkBreakdown extends Audit { } /** - * @param {LH.Artifacts.DevtoolsTimelineModel} timelineModel + * @param {LH.Artifacts.TaskNode[]} tasks * @return {Map} */ - static getExecutionTimingsByCategory(timelineModel) { - const bottomUpByName = timelineModel.bottomUpGroupBy('EventName'); - + static getExecutionTimingsByGroup(tasks) { + /** @type {Map} */ const result = new Map(); - bottomUpByName.children.forEach((event, eventName) => - result.set(eventName, event.selfTime)); + + for (const task of tasks) { + const originalTime = result.get(task.group.id) || 0; + result.set(task.group.id, originalTime + task.selfTime); + } return result; } @@ -65,40 +66,34 @@ class MainThreadWorkBreakdown extends Audit { const settings = context.settings || {}; const trace = artifacts.traces[MainThreadWorkBreakdown.DEFAULT_PASS]; - const devtoolsTimelineModel = await artifacts.requestDevtoolsTimelineModel(trace); - const executionTimings = MainThreadWorkBreakdown.getExecutionTimingsByCategory( - devtoolsTimelineModel - ); - let totalExecutionTime = 0; - + const tasks = await artifacts.requestMainThreadTasks(trace); const multiplier = settings.throttlingMethod === 'simulate' ? settings.throttling.cpuSlowdownMultiplier : 1; - const extendedInfo = {}; + const executionTimings = MainThreadWorkBreakdown.getExecutionTimingsByGroup(tasks); + + let totalExecutionTime = 0; const categoryTotals = {}; - const results = Array.from(executionTimings).map(([eventName, duration]) => { - duration *= multiplier; + const results = Array.from(executionTimings).map(([groupId, rawDuration]) => { + const duration = rawDuration * multiplier; totalExecutionTime += duration; - extendedInfo[eventName] = duration; - const groupName = taskToGroup[eventName]; - const categoryTotal = categoryTotals[groupName] || 0; - categoryTotals[groupName] = categoryTotal + duration; + const categoryTotal = categoryTotals[groupId] || 0; + categoryTotals[groupId] = categoryTotal + duration; return { - category: eventName, - group: groupName, + group: groupId, + groupLabel: taskGroups[groupId].label, duration: duration, }; }); const headings = [ - {key: 'group', itemType: 'text', text: 'Category'}, - {key: 'category', itemType: 'text', text: 'Work'}, - {key: 'duration', itemType: 'ms', granularity: 1, text: 'Time spent'}, + {key: 'groupLabel', itemType: 'text', text: 'Category'}, + {key: 'duration', itemType: 'ms', granularity: 1, text: 'Time Spent'}, ]; - // @ts-ignore - stableSort added to Array by WebInspector - results.stableSort((a, b) => categoryTotals[b.group] - categoryTotals[a.group]); + + results.sort((a, b) => categoryTotals[b.group] - categoryTotals[a.group]); const tableDetails = MainThreadWorkBreakdown.makeTableDetails(headings, results); const score = Audit.computeLogNormalScore( @@ -112,9 +107,6 @@ class MainThreadWorkBreakdown extends Audit { rawValue: totalExecutionTime, displayValue: [Util.MS_DISPLAY_VALUE, totalExecutionTime], details: tableDetails, - extendedInfo: { - value: extendedInfo, - }, }; } } diff --git a/lighthouse-core/gather/computed/dtm-model.js b/lighthouse-core/gather/computed/dtm-model.js deleted file mode 100644 index 530234e9ab8f..000000000000 --- a/lighthouse-core/gather/computed/dtm-model.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -'use strict'; - -const ComputedArtifact = require('./computed-artifact'); -const DTM = require('../../lib/traces/devtools-timeline-model'); - -class DevtoolsTimelineModel extends ComputedArtifact { - get name() { - return 'DevtoolsTimelineModel'; - } - - /** - * @param {LH.Trace} trace - * @return {Promise} - */ - async compute_(trace) { - return new DTM(trace); - } -} - -module.exports = DevtoolsTimelineModel; diff --git a/lighthouse-core/gather/computed/main-thread-tasks.js b/lighthouse-core/gather/computed/main-thread-tasks.js new file mode 100644 index 000000000000..673d2a596645 --- /dev/null +++ b/lighthouse-core/gather/computed/main-thread-tasks.js @@ -0,0 +1,241 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const ComputedArtifact = require('./computed-artifact'); +const {taskGroups, taskNameToGroup} = require('../../lib/task-groups'); + +/** + * @fileoverview + * + * This artifact converts the array of raw trace events into an array of hierarchical + * tasks for easier consumption and bottom-up analysis. + * + * Events are easily produced but difficult to consume. They're a mixture of start/end markers, "complete" events, etc. + * @see https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview + * + * LH's TaskNode is an artifact that fills in the gaps a trace event leaves behind. + * i.e. when did it end? which events are children/parents of this one? + * + * Each task will have its group/classification, start time, end time, + * duration, and self time computed. Each task will potentially have a parent, children, and an + * attributableURL for the script that was executing/forced this execution. + */ + +/** @typedef {import('../../lib/task-groups.js').TaskGroup} TaskGroup */ + +/** + * @typedef TaskNode + * @prop {LH.TraceEvent} event + * @prop {TaskNode[]} children + * @prop {TaskNode|undefined} parent + * @prop {number} startTime + * @prop {number} endTime + * @prop {number} duration + * @prop {number} selfTime + * @prop {string[]} attributableURLs + * @prop {TaskGroup} group + */ + +class MainThreadTasks extends ComputedArtifact { + get name() { + return 'MainThreadTasks'; + } + + /** + * @param {LH.TraceEvent} event + * @param {TaskNode} [parent] + * @return {TaskNode} + */ + static _createNewTaskNode(event, parent) { + const newTask = { + event, + startTime: event.ts, + endTime: event.ph === 'X' ? event.ts + Number(event.dur || 0) : NaN, + parent: parent, + children: [], + + // These properties will be filled in later + attributableURLs: [], + group: taskGroups.other, + duration: NaN, + selfTime: NaN, + }; + + if (parent) { + parent.children.push(newTask); + } + + return newTask; + } + + /** + * @param {LH.TraceEvent[]} mainThreadEvents + * @return {TaskNode[]} + */ + static _createTasksFromEvents(mainThreadEvents) { + /** @type {TaskNode[]} */ + const tasks = []; + /** @type {TaskNode|undefined} */ + let currentTask; + + for (const event of mainThreadEvents) { + // Only look at X (Complete), B (Begin), and E (End) events as they have most data + if (event.ph !== 'X' && event.ph !== 'B' && event.ph !== 'E') continue; + + // Update currentTask based on the elapsed time. + // The next event may be after currentTask has ended. + while ( + currentTask && + Number.isFinite(currentTask.endTime) && + currentTask.endTime <= event.ts + ) { + currentTask = currentTask.parent; + } + + // If we don't have a current task, start a new one. + if (!currentTask) { + // We can't start a task with an end event + if (event.ph === 'E') { + throw new Error('Fatal trace logic error'); + } + + currentTask = MainThreadTasks._createNewTaskNode(event); + tasks.push(currentTask); + + continue; + } + + if (event.ph === 'X' || event.ph === 'B') { + // We're starting a nested event, create it as a child and make it the currentTask + const newTask = MainThreadTasks._createNewTaskNode(event, currentTask); + tasks.push(newTask); + currentTask = newTask; + } else { + if (currentTask.event.ph !== 'B') { + throw new Error('Fatal trace logic error'); + } + + // We're ending an event, update the end time and the currentTask to its parent + currentTask.endTime = event.ts; + currentTask = currentTask.parent; + } + } + + return tasks; + } + + /** + * @param {TaskNode} task + * @return {number} + */ + static _computeRecursiveSelfTime(task) { + const childTime = task.children + .map(MainThreadTasks._computeRecursiveSelfTime) + .reduce((sum, child) => sum + child, 0); + task.duration = task.endTime - task.startTime; + task.selfTime = task.duration - childTime; + return task.duration; + } + + /** + * @param {TaskNode} task + * @param {string[]} parentURLs + */ + static _computeRecursiveAttributableURLs(task, parentURLs) { + const argsData = task.event.args.data || {}; + const stackFrameURLs = (argsData.stackTrace || []).map(entry => entry.url); + + let taskURLs = []; + switch (task.event.name) { + /** + * Some trace events reference a specific script URL that triggered them. + * Use this URL as the higher precedence attributable URL. + * @see https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js?type=cs&q=_initEventStyles+-f:out+f:devtools&sq=package:chromium&g=0&l=678-744 + */ + case 'v8.compile': + case 'EvaluateScript': + case 'FunctionCall': + taskURLs = [argsData.url].concat(stackFrameURLs); + break; + case 'v8.compileModule': + taskURLs = [task.event.args.fileName].concat(stackFrameURLs); + break; + default: + taskURLs = stackFrameURLs; + break; + } + + /** @type {string[]} */ + const attributableURLs = Array.from(parentURLs); + for (const url of taskURLs) { + // Don't add empty URLs + if (!url) continue; + // Don't add consecutive, duplicate URLs + if (attributableURLs[attributableURLs.length - 1] === url) continue; + attributableURLs.push(url); + } + + task.attributableURLs = attributableURLs; + task.children.forEach(child => + MainThreadTasks._computeRecursiveAttributableURLs(child, attributableURLs)); + } + + /** + * @param {TaskNode} task + * @param {TaskGroup} [parentGroup] + */ + static _computeRecursiveTaskGroup(task, parentGroup) { + const group = taskNameToGroup[task.event.name]; + task.group = group || parentGroup || taskGroups.other; + task.children.forEach(child => MainThreadTasks._computeRecursiveTaskGroup(child, task.group)); + } + + /** + * @param {LH.TraceEvent[]} traceEvents + * @return {TaskNode[]} + */ + static getMainThreadTasks(traceEvents) { + const tasks = MainThreadTasks._createTasksFromEvents(traceEvents); + + // Compute the recursive properties we couldn't compute earlier, starting at the toplevel tasks + for (const task of tasks) { + if (task.parent) continue; + + MainThreadTasks._computeRecursiveSelfTime(task); + MainThreadTasks._computeRecursiveAttributableURLs(task, []); + MainThreadTasks._computeRecursiveTaskGroup(task); + } + + // Rebase all the times to be relative to start of trace in ms + const firstTs = (tasks[0] || {startTime: 0}).startTime; + for (const task of tasks) { + task.startTime = (task.startTime - firstTs) / 1000; + task.endTime = (task.endTime - firstTs) / 1000; + task.duration /= 1000; + task.selfTime /= 1000; + + // sanity check that we have selfTime which captures all other timing data + if (!Number.isFinite(task.selfTime)) { + throw new Error('Invalid task timing data'); + } + } + + return tasks; + } + + /** + * @param {LH.Trace} trace + * @param {LH.Artifacts} artifacts + * @return {Promise>} networkRecords + */ + async compute_(trace, artifacts) { + const {mainThreadEvents} = await artifacts.requestTraceOfTab(trace); + return MainThreadTasks.getMainThreadTasks(mainThreadEvents); + } +} + +module.exports = MainThreadTasks; diff --git a/lighthouse-core/gather/computed/screenshots.js b/lighthouse-core/gather/computed/screenshots.js index df74a12a1f32..74ecaf0facdf 100644 --- a/lighthouse-core/gather/computed/screenshots.js +++ b/lighthouse-core/gather/computed/screenshots.js @@ -7,39 +7,26 @@ const ComputedArtifact = require('./computed-artifact'); +const SCREENSHOT_TRACE_NAME = 'Screenshot'; + class ScreenshotFilmstrip extends ComputedArtifact { get name() { return 'Screenshots'; } - /** - * @param {{imageDataPromise: function(): Promise}} frame - * @return {Promise} - */ - fetchScreenshot(frame) { - return frame - .imageDataPromise() - .then(data => 'data:image/jpg;base64,' + data); - } - /** * @param {LH.Trace} trace - * @param {LH.ComputedArtifacts} computedArtifacts * @return {Promise>} */ - compute_(trace, computedArtifacts) { - return computedArtifacts.requestDevtoolsTimelineModel(trace).then(model => { - const filmStripFrames = model.filmStripModel().frames(); - const frameFetches = filmStripFrames.map(frame => this.fetchScreenshot(frame)); - - return Promise.all(frameFetches).then(images => { - const result = filmStripFrames.map((frame, i) => ({ - timestamp: frame.timestamp, - datauri: images[i], - })); - return result; + async compute_(trace) { + return trace.traceEvents + .filter(evt => evt.name === SCREENSHOT_TRACE_NAME) + .map(evt => { + return { + timestamp: evt.ts / 1000, + datauri: `data:image/jpg;base64,${evt.args.snapshot}`, + }; }); - }); } } diff --git a/lighthouse-core/lib/task-groups.js b/lighthouse-core/lib/task-groups.js index 74e486650f1a..d61af424d655 100644 --- a/lighthouse-core/lib/task-groups.js +++ b/lighthouse-core/lib/task-groups.js @@ -5,90 +5,108 @@ */ 'use strict'; -const groupIdToName = { - loading: 'Network request loading', - parseHTML: 'Parsing HTML & CSS', - styleLayout: 'Style & Layout', - compositing: 'Compositing', - painting: 'Paint', - gpu: 'GPU', - scripting: 'Script Evaluation', - scriptParseCompile: 'Script Parsing & Compile', - scriptGC: 'Garbage collection', - other: 'Other', - images: 'Images', -}; +/** + * @typedef TaskGroup + * @property {string} id + * @property {string} label + * @property {string[]} traceEventNames + */ -const taskToGroup = { - 'Animation': groupIdToName.painting, - 'Async Task': groupIdToName.other, - 'Frame Start': groupIdToName.painting, - 'Frame Start (main thread)': groupIdToName.painting, - 'Cancel Animation Frame': groupIdToName.scripting, - 'Cancel Idle Callback': groupIdToName.scripting, - 'Compile Script': groupIdToName.scriptParseCompile, - 'Composite Layers': groupIdToName.compositing, - 'Console Time': groupIdToName.scripting, - 'Image Decode': groupIdToName.images, - 'Draw Frame': groupIdToName.painting, - 'Embedder Callback': groupIdToName.scripting, - 'Evaluate Script': groupIdToName.scripting, - 'Event': groupIdToName.scripting, - 'Animation Frame Fired': groupIdToName.scripting, - 'Fire Idle Callback': groupIdToName.scripting, - 'Function Call': groupIdToName.scripting, - 'DOM GC': groupIdToName.scriptGC, - 'GC Event': groupIdToName.scriptGC, - 'GPU': groupIdToName.gpu, - 'Hit Test': groupIdToName.compositing, - 'Invalidate Layout': groupIdToName.styleLayout, - 'JS Frame': groupIdToName.scripting, - 'Input Latency': groupIdToName.scripting, - 'Layout': groupIdToName.styleLayout, - 'Major GC': groupIdToName.scriptGC, - 'DOMContentLoaded event': groupIdToName.scripting, - 'First paint': groupIdToName.painting, - 'FMP': groupIdToName.painting, - 'FMP candidate': groupIdToName.painting, - 'Load event': groupIdToName.scripting, - 'Minor GC': groupIdToName.scriptGC, - 'Paint': groupIdToName.painting, - 'Paint Image': groupIdToName.images, - 'Paint Setup': groupIdToName.painting, - 'Parse Stylesheet': groupIdToName.parseHTML, - 'Parse HTML': groupIdToName.parseHTML, - 'Parse Script': groupIdToName.scriptParseCompile, - 'Other': groupIdToName.other, - 'Rasterize Paint': groupIdToName.painting, - 'Recalculate Style': groupIdToName.styleLayout, - 'Request Animation Frame': groupIdToName.scripting, - 'Request Idle Callback': groupIdToName.scripting, - 'Request Main Thread Frame': groupIdToName.painting, - 'Image Resize': groupIdToName.images, - 'Finish Loading': groupIdToName.loading, - 'Receive Data': groupIdToName.loading, - 'Receive Response': groupIdToName.loading, - 'Send Request': groupIdToName.loading, - 'Run Microtasks': groupIdToName.scripting, - 'Schedule Style Recalculation': groupIdToName.styleLayout, - 'Scroll': groupIdToName.compositing, - 'Task': groupIdToName.other, - 'Timer Fired': groupIdToName.scripting, - 'Install Timer': groupIdToName.scripting, - 'Remove Timer': groupIdToName.scripting, - 'Timestamp': groupIdToName.scripting, - 'Update Layer': groupIdToName.compositing, - 'Update Layer Tree': groupIdToName.compositing, - 'User Timing': groupIdToName.scripting, - 'Create WebSocket': groupIdToName.scripting, - 'Destroy WebSocket': groupIdToName.scripting, - 'Receive WebSocket Handshake': groupIdToName.scripting, - 'Send WebSocket Handshake': groupIdToName.scripting, - 'XHR Load': groupIdToName.scripting, - 'XHR Ready State Change': groupIdToName.scripting, +/** + * Make sure the traceEventNames keep up with the ones in DevTools + * @see https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js?type=cs&q=TimelineModel.TimelineModel.RecordType+%3D&g=0&l=1156 + * @see https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js?type=cs&q=_initEventStyles+-f:out+f:devtools&sq=package:chromium&g=0&l=39 + */ +const taskGroups = { + parseHTML: { + id: '', + label: 'Parse HTML & CSS', + traceEventNames: ['ParseHTML', 'ParseAuthorStyleSheet'], + }, + styleLayout: { + id: '', + label: 'Style & Layout', + traceEventNames: [ + 'ScheduleStyleRecalculation', + 'RecalculateStyles', + 'UpdateLayoutTree', + 'InvalidateLayout', + 'Layout', + ], + }, + paintCompositeRender: { + id: '', + label: 'Rendering', + traceEventNames: [ + 'Animation', + 'RequestMainThreadFrame', + 'ActivateLayerTree', + 'DrawFrame', + 'HitTest', + 'PaintSetup', + 'Paint', + 'PaintImage', + 'Rasterize', + 'RasterTask', + 'ScrollLayer', + 'UpdateLayer', + 'UpdateLayerTree', + 'CompositeLayers', + ], + }, + scriptParseCompile: { + id: '', + label: 'Script Parsing & Compilation', + traceEventNames: ['v8.compile', 'v8.compileModule', 'v8.parseOnBackground'], + }, + scriptEvaluation: { + id: '', + label: 'Script Evaluation', + traceEventNames: [ + 'EventDispatch', + 'EvaluateScript', + 'v8.evaluateModule', + 'FunctionCall', + 'TimerFire', + 'FireIdleCallback', + 'FireAnimationFrame', + 'RunMicrotasks', + 'V8.Execute', + ], + }, + garbageCollection: { + id: '', + label: 'Garbage Collection', + traceEventNames: [ + 'GCEvent', + 'MinorGC', + 'MajorGC', + 'ThreadState::performIdleLazySweep', + 'ThreadState::completeSweep', + 'BlinkGCMarking', + ], + }, + other: { + id: '', + label: 'Other', + traceEventNames: [ + 'MessageLoop::RunTask', + 'TaskQueueManager::ProcessTaskFromWorkQueue', + 'ThreadControllerImpl::DoWork', + ], + }, }; +/** @type {Object} */ +const taskNameToGroup = {}; +for (const [groupId, group] of Object.entries(taskGroups)) { + group.id = groupId; + for (const traceEventName of group.traceEventNames) { + taskNameToGroup[traceEventName] = group; + } +} + module.exports = { - groupIdToName, - taskToGroup, + taskGroups, + taskNameToGroup, }; diff --git a/lighthouse-core/lib/traces/devtools-timeline-model.js b/lighthouse-core/lib/traces/devtools-timeline-model.js deleted file mode 100644 index b93a276ee45c..000000000000 --- a/lighthouse-core/lib/traces/devtools-timeline-model.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @license Copyright 2016 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -'use strict'; - -const WebInspector = require('../web-inspector'); -const ConsoleQuieter = require('../console-quieter'); - -// Polyfill the bottom-up and topdown tree sorting. -const TimelineModelTreeView = - // @ts-ignore - require('devtools-timeline-model/lib/timeline-model-treeview.js')(WebInspector); - -class TimelineModel { - /** @param {LH.Trace} events */ - constructor(events) { - this.init(events); - } - - /** @param {LH.Trace} trace */ - init(trace) { - // (devtools) tracing model - this._tracingModel = - new WebInspector.TracingModel(new WebInspector.TempFileBackingStorage('tracing')); - // timeline model - this._timelineModel = - new WebInspector.TimelineModel(WebInspector.TimelineUIUtils.visibleEventsFilter()); - - let events; - if (Array.isArray(trace)) { - events = trace; - } - if (typeof trace === 'string') { - events = JSON.parse(trace); - } - if (trace.hasOwnProperty('traceEvents')) { - events = trace.traceEvents; - } - - // populate with events - this._tracingModel.reset(); - - try { - ConsoleQuieter.mute({prefix: 'timelineModel'}); - this._tracingModel.addEvents(events); - this._tracingModel.tracingComplete(); - this._timelineModel.setEvents(this._tracingModel); - } finally { - ConsoleQuieter.unmuteAndFlush(); - } - - return this; - } - - _createAggregator() { - return WebInspector.AggregatedTimelineTreeView.prototype._createAggregator(); - } - - timelineModel() { - return this._timelineModel; - } - - tracingModel() { - return this._tracingModel; - } - - topDown() { - const filters = []; - filters.push(WebInspector.TimelineUIUtils.visibleEventsFilter()); - filters.push(new WebInspector.ExcludeTopLevelFilter()); - const nonessentialEvents = [ - WebInspector.TimelineModel.RecordType.EventDispatch, - WebInspector.TimelineModel.RecordType.FunctionCall, - WebInspector.TimelineModel.RecordType.TimerFire, - ]; - filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents)); - - const topDown = WebInspector.TimelineProfileTree.buildTopDown( - this._timelineModel.mainThreadEvents(), - filters, /* startTime */ 0, /* endTime */ Infinity, - WebInspector.TimelineAggregator.eventId); - return topDown; - } - - bottomUp() { - const topDown = this.topDown(); - const noGrouping = WebInspector.TimelineAggregator.GroupBy.None; - const noGroupAggregator = this._createAggregator().groupFunction(noGrouping); - return WebInspector.TimelineProfileTree.buildBottomUp(topDown, noGroupAggregator); - } - - /** - * @param {!string} grouping Allowed values: None Category Subdomain Domain URL EventName - */ - bottomUpGroupBy(grouping) { - const topDown = this.topDown(); - - const groupSetting = WebInspector.TimelineAggregator.GroupBy[grouping]; - const groupingAggregator = this._createAggregator().groupFunction(groupSetting); - const bottomUpGrouped = - WebInspector.TimelineProfileTree.buildBottomUp(topDown, groupingAggregator); - - // sort the grouped tree, in-place - new TimelineModelTreeView(bottomUpGrouped).sortingChanged('self', 'desc'); - return bottomUpGrouped; - } - - frameModel() { - /** @param {LH.TraceEvent} event */ - const mapper = event => WebInspector.TimelineUIUtils.eventStyle(event).category.name; - const frameModel = new WebInspector.TimelineFrameModel(mapper); - frameModel.addTraceEvents({ /* target */ }, - this._timelineModel.inspectedTargetEvents(), this._timelineModel.sessionId() || ''); - return frameModel; - } - - /** @return {LH.Artifacts.DevtoolsTimelineFilmStripModel} */ - filmStripModel() { - return new WebInspector.FilmStripModel(this._tracingModel); - } - - interactionModel() { - const irModel = new WebInspector.TimelineIRModel(); - irModel.populate(this._timelineModel); - return irModel; - } -} - -module.exports = TimelineModel; diff --git a/lighthouse-core/lib/web-inspector.js b/lighthouse-core/lib/web-inspector.js index d38ef458421a..c181fef2c5a5 100644 --- a/lighthouse-core/lib/web-inspector.js +++ b/lighthouse-core/lib/web-inspector.js @@ -104,6 +104,7 @@ module.exports = (function() { require('chrome-devtools-frontend/front_end/common/Object.js'); require('chrome-devtools-frontend/front_end/common/ParsedURL.js'); require('chrome-devtools-frontend/front_end/common/UIString.js'); + require('chrome-devtools-frontend/front_end/common/SegmentedRange.js'); require('chrome-devtools-frontend/front_end/platform/utilities.js'); require('chrome-devtools-frontend/front_end/sdk/Target.js'); require('chrome-devtools-frontend/front_end/sdk/TargetManager.js'); @@ -132,34 +133,10 @@ module.exports = (function() { WebInspector.ViewportDataGridNode = function() {}; global.WorkerRuntime.Worker = function() {}; - require('chrome-devtools-frontend/front_end/common/SegmentedRange.js'); - require('chrome-devtools-frontend/front_end/bindings/TempFile.js'); - require('chrome-devtools-frontend/front_end/sdk/TracingModel.js'); - require('chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js'); - require('chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js'); - require('chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js'); - require('chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js'); - require('chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js'); - require('chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js'); - require('chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js'); - require('chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js'); - // used for streaming json parsing require('chrome-devtools-frontend/front_end/common/TextUtils.js'); require('chrome-devtools-frontend/front_end/timeline/TimelineLoader.js'); - require('chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js'); - require('chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js'); - require('chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js'); - require('chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js'); - - // DevTools makes a few assumptions about using backing storage to hold traces. - WebInspector.DeferredTempFile = function() {}; - WebInspector.DeferredTempFile.prototype = { - write: function() {}, - finishWriting: function() {}, - }; - // Mock for WebInspector code that writes to console. WebInspector.ConsoleMessage = function() {}; WebInspector.ConsoleMessage.MessageSource = { diff --git a/lighthouse-core/test/audits/bootup-time-test.js b/lighthouse-core/test/audits/bootup-time-test.js index 4cf4797262da..942a52ed6bcb 100644 --- a/lighthouse-core/test/audits/bootup-time-test.js +++ b/lighthouse-core/test/audits/bootup-time-test.js @@ -9,63 +9,60 @@ const BootupTime = require('../../audits/bootup-time.js'); const Runner = require('../../runner.js'); const assert = require('assert'); -const {groupIdToName} = require('../../lib/task-groups'); const acceptableTrace = require('../fixtures/traces/progressive-app-m60.json'); +const acceptableDevtoolsLogs = require('../fixtures/traces/progressive-app-m60.devtools.log.json'); const errorTrace = require('../fixtures/traces/airhorner_no_fcp.json'); describe('Performance: bootup-time audit', () => { const auditOptions = Object.assign({}, BootupTime.defaultOptions, {thresholdInMs: 10}); - const roundedValueOf = (output, name) => { - const value = output.extendedInfo.value[name]; + const roundedValueOf = (output, url) => { + const value = output.details.items.find(item => item.url === url); const roundedValue = {}; Object.keys(value).forEach(key => roundedValue[key] = Math.round(value[key] * 10) / 10); + delete roundedValue.url; return roundedValue; }; it('should compute the correct BootupTime values', () => { const artifacts = Object.assign({ - traces: { - [BootupTime.DEFAULT_PASS]: acceptableTrace, - }, + traces: {[BootupTime.DEFAULT_PASS]: acceptableTrace}, + devtoolsLogs: {[BootupTime.DEFAULT_PASS]: acceptableDevtoolsLogs}, }, Runner.instantiateComputedArtifacts()); return BootupTime.audit(artifacts, {options: auditOptions}).then(output => { + assert.deepEqual(roundedValueOf(output, 'https://pwa.rocks/script.js'), {scripting: 31.8, scriptParseCompile: 1.3, total: 36.8}); + assert.deepEqual(roundedValueOf(output, 'https://www.googletagmanager.com/gtm.js?id=GTM-Q5SW'), {scripting: 96.9, scriptParseCompile: 6.5, total: 104.4}); + assert.deepEqual(roundedValueOf(output, 'https://www.google-analytics.com/plugins/ua/linkid.js'), {scripting: 25.2, scriptParseCompile: 1.2, total: 26.4}); + assert.deepEqual(roundedValueOf(output, 'https://www.google-analytics.com/analytics.js'), {scripting: 40.6, scriptParseCompile: 9.6, total: 53.4}); + + assert.equal(Math.round(output.rawValue), 221); assert.equal(output.details.items.length, 4); assert.equal(output.score, 1); - assert.equal(Math.round(output.rawValue), 176); - - assert.deepEqual(roundedValueOf(output, 'https://pwa.rocks/script.js'), {[groupIdToName.scripting]: 31.8, [groupIdToName.styleLayout]: 5.5, [groupIdToName.scriptParseCompile]: 1.3}); - assert.deepEqual(roundedValueOf(output, 'https://www.googletagmanager.com/gtm.js?id=GTM-Q5SW'), {[groupIdToName.scripting]: 25, [groupIdToName.scriptParseCompile]: 5.5, [groupIdToName.styleLayout]: 1.2}); - assert.deepEqual(roundedValueOf(output, 'https://www.google-analytics.com/plugins/ua/linkid.js'), {[groupIdToName.scripting]: 25.2, [groupIdToName.scriptParseCompile]: 1.2}); - assert.deepEqual(roundedValueOf(output, 'https://www.google-analytics.com/analytics.js'), {[groupIdToName.scripting]: 40.1, [groupIdToName.scriptParseCompile]: 9.6, [groupIdToName.styleLayout]: 0.2}); - - assert.ok(output.details.items.length < Object.keys(output.extendedInfo.value).length, - 'Items below threshold were not filtered out'); }); }).timeout(10000); it('should compute the correct values when simulated', async () => { const artifacts = Object.assign({ traces: {defaultPass: acceptableTrace}, + devtoolsLogs: {defaultPass: acceptableDevtoolsLogs}, }, Runner.instantiateComputedArtifacts()); const options = auditOptions; const settings = {throttlingMethod: 'simulate', throttling: {cpuSlowdownMultiplier: 3}}; const output = await BootupTime.audit(artifacts, {options, settings}); - assert.equal(output.details.items.length, 7); - assert.equal(output.score, 0.99); - assert.equal(Math.round(output.rawValue), 528); + assert.deepEqual(roundedValueOf(output, 'https://pwa.rocks/script.js'), {scripting: 95.3, scriptParseCompile: 3.9, total: 110.5}); - assert.deepEqual(roundedValueOf(output, 'https://pwa.rocks/script.js'), {[groupIdToName.scripting]: 95.3, [groupIdToName.styleLayout]: 16.4, [groupIdToName.scriptParseCompile]: 3.9}); + assert.equal(output.details.items.length, 7); + assert.equal(output.score, 0.98); + assert.equal(Math.round(output.rawValue), 709); }); it('should get no data when no events are present', () => { const artifacts = Object.assign({ - traces: { - [BootupTime.DEFAULT_PASS]: errorTrace, - }, + traces: {defaultPass: errorTrace}, + devtoolsLogs: {defaultPass: []}, }, Runner.instantiateComputedArtifacts()); return BootupTime.audit(artifacts, {options: auditOptions}) diff --git a/lighthouse-core/test/audits/mainthread-work-breakdown-test.js b/lighthouse-core/test/audits/mainthread-work-breakdown-test.js index 764afa798ff0..b4a0b3554aa2 100644 --- a/lighthouse-core/test/audits/mainthread-work-breakdown-test.js +++ b/lighthouse-core/test/audits/mainthread-work-breakdown-test.js @@ -17,143 +17,106 @@ const loadTrace = require('../fixtures/traces/load.json'); const errorTrace = require('../fixtures/traces/airhorner_no_fcp.json'); const acceptableTraceExpectations = { - 'Compile Script': 25, - 'Composite Layers': 6, - 'DOM GC': 33, - 'Evaluate Script': 131, - 'Image Decode': 1, - 'Layout': 138, - 'Major GC': 8, - 'Minor GC': 7, - 'Paint': 52, - 'Parse HTML': 14, - 'Recalculate Style': 170, - 'Update Layer Tree': 25, + parseHTML: 14, + styleLayout: 308, + paintCompositeRender: 87, + scriptEvaluation: 215, + scriptParseCompile: 25, + garbageCollection: 48, + other: 663, }; const siteWithRedirectTraceExpectations = { - 'Compile Script': 38, - 'Composite Layers': 2, - 'DOM GC': 25, - 'Evaluate Script': 122, - 'Image Decode': 0, - 'Layout': 209, - 'Major GC': 10, - 'Minor GC': 11, - 'Paint': 4, - 'Parse HTML': 52, - 'Parse Stylesheet': 51, - 'Recalculate Style': 66, - 'Update Layer Tree': 5, + parseHTML: 84, + styleLayout: 275, + paintCompositeRender: 12, + scriptEvaluation: 145, + scriptParseCompile: 38, + garbageCollection: 46, + other: 184, }; const loadTraceExpectations = { - 'Animation Frame Fired': 6, - 'Composite Layers': 15, - 'Evaluate Script': 296, - 'Image Decode': 4, - 'Layout': 51, - 'Minor GC': 3, - 'Paint': 9, - 'Parse HTML': 25, - 'Recalculate Style': 80, - 'Update Layer Tree': 16, - 'XHR Load': 19, - 'XHR Ready State Change': 1, + parseHTML: 25, + styleLayout: 131, + paintCompositeRender: 44, + scriptEvaluation: 347, + garbageCollection: 3, + other: 382, }; describe('Performance: page execution timings audit', () => { - it('should compute the correct pageExecutionTiming values for the pwa trace', () => { - const artifacts = Object.assign({ - traces: { - [PageExecutionTimings.DEFAULT_PASS]: acceptableTrace, - }, - }, Runner.instantiateComputedArtifacts()); - - return PageExecutionTimings.audit(artifacts, {options}).then(output => { - const valueOf = name => Math.round(output.extendedInfo.value[name]); - - assert.equal(output.details.items.length, 12); - assert.equal(output.score, 1); - assert.equal(Math.round(output.rawValue), 611); - - for (const category in output.extendedInfo.value) { - if (output.extendedInfo.value[category]) { - assert.equal(valueOf(category), acceptableTraceExpectations[category]); - } - } - }); + function keyOutput(output) { + const keyedOutput = {}; + for (const item of output.details.items) { + keyedOutput[item.group] = Math.round(item.duration); + } + return keyedOutput; + } + + it('should compute the correct pageExecutionTiming values for the pwa trace', async () => { + const artifacts = Object.assign( + {traces: {defaultPass: acceptableTrace}}, + Runner.instantiateComputedArtifacts() + ); + + const output = await PageExecutionTimings.audit(artifacts, {options}); + assert.deepStrictEqual(keyOutput(output), acceptableTraceExpectations); + assert.equal(Math.round(output.rawValue), 1360); + assert.equal(output.details.items.length, 7); + assert.equal(output.score, 0.98); }); it('should compute the correct values when simulated', async () => { - const artifacts = Object.assign({ - traces: {defaultPass: acceptableTrace}, - }, Runner.instantiateComputedArtifacts()); + const artifacts = Object.assign( + {traces: {defaultPass: acceptableTrace}}, + Runner.instantiateComputedArtifacts() + ); const settings = {throttlingMethod: 'simulate', throttling: {cpuSlowdownMultiplier: 3}}; const output = await PageExecutionTimings.audit(artifacts, {options, settings}); - const valueOf = name => Math.round(output.extendedInfo.value[name]); - - assert.equal(output.details.items.length, 12); - assert.equal(output.score, 0.93); - assert.equal(Math.round(output.rawValue), 1832); - - for (const category in output.extendedInfo.value) { - if (output.extendedInfo.value[category]) { - assert.ok( - Math.abs(valueOf(category) - 3 * acceptableTraceExpectations[category]) < 2, - 'should have multiplied value by slowdown multiplier' - ); - } - } - }); - - it('should compute the correct pageExecutionTiming values for the redirect trace', () => { - const artifacts = Object.assign({ - traces: { - [PageExecutionTimings.DEFAULT_PASS]: siteWithRedirectTrace, - }, - }, Runner.instantiateComputedArtifacts()); - return PageExecutionTimings.audit(artifacts, {options}).then(output => { - const valueOf = name => Math.round(output.extendedInfo.value[name]); - assert.equal(output.details.items.length, 13); - assert.equal(output.score, 1); - assert.equal(Math.round(output.rawValue), 596); + const keyedOutput = keyOutput(output); + for (const key of Object.keys(acceptableTraceExpectations)) { + const actual = keyedOutput[key]; + const expected = acceptableTraceExpectations[key] * 3; + assert.ok(Math.abs(actual - expected) <= 2, `expected ${expected} got ${actual}`); + } - for (const category in output.extendedInfo.value) { - if (output.extendedInfo.value[category]) { - assert.equal(valueOf(category), siteWithRedirectTraceExpectations[category]); - } - } - }); + assert.equal(Math.round(output.rawValue), 4081); + assert.equal(output.details.items.length, 7); + assert.equal(output.score, 0.49); }); - it('should compute the correct pageExecutionTiming values for the load trace', () => { - const artifacts = Object.assign({ - traces: { - [PageExecutionTimings.DEFAULT_PASS]: loadTrace, - }, - }, Runner.instantiateComputedArtifacts()); - - return PageExecutionTimings.audit(artifacts, {options}).then(output => { - const valueOf = name => Math.round(output.extendedInfo.value[name]); - assert.equal(output.details.items.length, 12); - assert.equal(output.score, 1); - assert.equal(Math.round(output.rawValue), 524); + it('should compute the correct values for the redirect trace', async () => { + const artifacts = Object.assign( + {traces: {defaultPass: siteWithRedirectTrace}}, + Runner.instantiateComputedArtifacts() + ); + + const output = await PageExecutionTimings.audit(artifacts, {options}); + assert.deepStrictEqual(keyOutput(output), siteWithRedirectTraceExpectations); + assert.equal(Math.round(output.rawValue), 784); + assert.equal(output.details.items.length, 7); + assert.equal(output.score, 1); + }); - for (const category in output.extendedInfo.value) { - if (output.extendedInfo.value[category]) { - assert.equal(valueOf(category), loadTraceExpectations[category]); - } - } - }); + it('should compute the correct values for the load trace', async () => { + const artifacts = Object.assign( + {traces: {defaultPass: {traceEvents: loadTrace}}}, + Runner.instantiateComputedArtifacts() + ); + + const output = await PageExecutionTimings.audit(artifacts, {options}); + assert.deepStrictEqual(keyOutput(output), loadTraceExpectations); + assert.equal(Math.round(output.rawValue), 933); + assert.equal(output.details.items.length, 6); + assert.equal(output.score, 1); }); it('should get no data when no events are present', () => { - const artifacts = Object.assign({ - traces: { - [PageExecutionTimings.DEFAULT_PASS]: errorTrace, - }, - }, Runner.instantiateComputedArtifacts()); + const artifacts = Object.assign( + {traces: {defaultPass: errorTrace}}, + Runner.instantiateComputedArtifacts() + ); return PageExecutionTimings.audit(artifacts, {options}).then(output => { assert.equal(output.details.items.length, 0); diff --git a/lighthouse-core/test/fixtures/traces/load.json b/lighthouse-core/test/fixtures/traces/load.json index a8baf84e48ad..9106ffe97687 100644 --- a/lighthouse-core/test/fixtures/traces/load.json +++ b/lighthouse-core/test/fixtures/traces/load.json @@ -508,6 +508,7 @@ {"pid":442,"tid":461,"ts":73609802076,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../components/scheduler/child/task_queue_manager.cc","src_func":"MaybePostDoWorkOnMainRunner"},"dur":6298,"tdur":1040,"tts":20275144}, {"pid":442,"tid":461,"ts":73609802087,"ph":"X","cat":"toplevel","name":"TaskQueueManager::ProcessTaskFromWorkQueue","args":{"src_file":"../../ipc/ipc_channel_proxy.cc","src_func":"OnMessageReceivedNoFilter"},"dur":5851,"tdur":666,"tts":20275155}, {"pid":442,"tid":461,"ts":73609804410,"ph":"I","cat":"disabled-by-default-devtools.timeline","name":"TracingStartedInPage","args":{"data":{"sessionId":"442.138","page":"0xffffffffa1a94000"}},"tts":20275707,"s":"t"}, +{"pid":442,"tid":461,"ts":73609804410,"ph":"R","cat":"blink.user_timing","name":"navigationStart","args":{"data":{"sessionId":"442.138"},"frame":"0xffffffffa1a94000"},"tts":20275707,"s":"t"}, {"pid":442,"tid":461,"ts":73609804428,"ph":"I","cat":"disabled-by-default-devtools.timeline","name":"SetLayerTreeId","args":{"data":{"sessionId":"442.138","layerTreeId":1}},"tts":20275720,"s":"t"}, {"pid":442,"tid":461,"ts":73609807946,"ph":"X","cat":"toplevel","name":"TaskQueueManager::ProcessTaskFromWorkQueue","args":{"src_file":"../../media/audio/fake_audio_worker.cc","src_func":"DoRead"},"dur":88,"tdur":85,"tts":20275831}, {"pid":442,"tid":461,"ts":73609808041,"ph":"X","cat":"toplevel","name":"TaskQueueManager::ProcessTaskFromWorkQueue","args":{"src_file":"../../ipc/ipc_channel_proxy.cc","src_func":"OnMessageReceivedNoFilter"},"dur":193,"tdur":116,"tts":20275927}, @@ -9773,4 +9774,4 @@ {"pid":396,"tid":472,"ts":73613667053,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../ipc/ipc_channel_proxy.cc","src_func":"Send"},"dur":22,"tdur":19,"tts":3058129}, {"pid":396,"tid":472,"ts":73613667391,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../ipc/ipc_channel_proxy.cc","src_func":"Send"},"dur":21,"tdur":17,"tts":3058179}, {"pid":396,"tid":472,"ts":73613667732,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../ipc/ipc_channel_proxy.cc","src_func":"Send"},"dur":21,"tdur":18,"tts":3058229}, -{"pid":396,"tid":468,"ts":73613668096,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../content/browser/tracing/tracing_controller_impl.cc","src_func":"DisableRecording"},"tdur":0,"tts":33052}] \ No newline at end of file +{"pid":396,"tid":468,"ts":73613668096,"ph":"X","cat":"toplevel","name":"toplevel","args":{"src_file":"../../content/browser/tracing/tracing_controller_impl.cc","src_func":"DisableRecording"},"tdur":0,"tts":33052}] diff --git a/lighthouse-core/test/gather/computed/dtm-model-test.js b/lighthouse-core/test/gather/computed/dtm-model-test.js deleted file mode 100644 index 9c96c592bde9..000000000000 --- a/lighthouse-core/test/gather/computed/dtm-model-test.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -'use strict'; - -/* eslint-env mocha */ - -const assert = require('assert'); -const fs = require('fs'); -const pwaTrace = require('../../fixtures/traces/progressive-app.json'); -const Runner = require('../../../runner.js'); - -/** - * Remove all objects that DTM mutates so we can use deepStrictEqual - * - * @param {*} trace - */ -function removeMutatedDataFromDevtools(trace) { - return trace.map(traceEvent => { - // deepclone - const newTraceEvent = JSON.parse(JSON.stringify(traceEvent)); - - if (newTraceEvent.args) { - if (newTraceEvent.args.data) { - const data = newTraceEvent.args.data; - delete data.columnNumber; - delete data.lineNumber; - delete data.url; - - if (data.stackTrace) { - data.stackTrace.forEach(stack => { - delete stack.columnNumber; - delete stack.lineNumber; - }); - } - } - - if (newTraceEvent.args.beginData && newTraceEvent.args.beginData.stackTrace) { - newTraceEvent.args.beginData.stackTrace.forEach(stack => { - delete stack.columnNumber; - delete stack.lineNumber; - }); - } - } - - return newTraceEvent; - }); -} - -describe('DTM Model gatherer', () => { - let computedArtifacts; - - beforeEach(() => { - computedArtifacts = Runner.instantiateComputedArtifacts(); - }); - - it('measures the pwa.rocks example', () => { - return computedArtifacts.requestDevtoolsTimelineModel({traceEvents: pwaTrace}).then(model => { - assert.equal(model.timelineModel().mainThreadEvents().length, 3157); - }); - }); - - it('does not change the orignal trace events', () => { - // Use fresh trace in case it has been altered by other require()s. - const pwaJson = fs.readFileSync(__dirname + - '/../../fixtures/traces/progressive-app.json', 'utf8'); - let pwaTrace = JSON.parse(pwaJson); - return computedArtifacts.requestDevtoolsTimelineModel({traceEvents: pwaTrace}) - .then(_ => { - const freshTrace = removeMutatedDataFromDevtools(JSON.parse(pwaJson)); - assert.strictEqual(pwaTrace.length, freshTrace.length); - - pwaTrace = removeMutatedDataFromDevtools(pwaTrace); - for (let i = 0; i < pwaTrace.length; i++) { - assert.deepStrictEqual(pwaTrace[i], freshTrace[i]); - } - }); - }).timeout(20000); -}); diff --git a/lighthouse-core/test/gather/computed/main-thread-tasks-test.js b/lighthouse-core/test/gather/computed/main-thread-tasks-test.js new file mode 100644 index 000000000000..cd1320c736cd --- /dev/null +++ b/lighthouse-core/test/gather/computed/main-thread-tasks-test.js @@ -0,0 +1,141 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +/* eslint-env mocha */ + +const Runner = require('../../../runner.js'); +const taskGroups = require('../../../lib/task-groups.js').taskGroups; +const pwaTrace = require('../../fixtures/traces/progressive-app.json'); +const TracingProcessor = require('../../../lib/traces/tracing-processor.js'); +const assert = require('assert'); + +describe('MainResource computed artifact', () => { + const args = {data: {}}; + const baseTs = 1241250325; + let boilerplateTrace; + let computedArtifacts; + + beforeEach(() => { + boilerplateTrace = [ + {ph: 'I', name: 'TracingStartedInPage', ts: baseTs, args}, + {ph: 'I', name: 'navigationStart', ts: baseTs, args}, + ]; + computedArtifacts = Runner.instantiateComputedArtifacts(); + }); + + it('should get all main thread tasks from a trace', async () => { + const tasks = await computedArtifacts.requestMainThreadTasks({traceEvents: pwaTrace}); + const toplevelTasks = tasks.filter(task => !task.parent); + assert.equal(tasks.length, 2305); + assert.equal(toplevelTasks.length, 296); + + // Sanity check the reachability of tasks and summation of selfTime + const allTasks = []; + const queue = toplevelTasks; + let totalTime = 0; + let totalTopLevelTime = 0; + while (queue.length) { + const task = queue.shift(); + totalTime += task.selfTime; + totalTopLevelTime += TracingProcessor.isScheduleableTask(task.event) ? task.duration : 0; + allTasks.push(task); + queue.push(...task.children); + } + + assert.equal(allTasks.length, 2305); + assert.equal(Math.round(totalTopLevelTime), 386); + assert.equal(Math.round(totalTime), 396); + }); + + it('should compute parent/child correctly', async () => { + /* + An artistic rendering of the below trace: + █████████████████████████████TaskA██████████████████████████████████████████████ + ████████████████TaskB███████████████████ + ████TaskC██████ + */ + const traceEvents = [ + ...boilerplateTrace, + {ph: 'X', name: 'TaskA', ts: baseTs, dur: 100e3}, + {ph: 'B', name: 'TaskB', ts: baseTs + 5e3}, + {ph: 'X', name: 'TaskC', ts: baseTs + 10e3, dur: 30e3}, + {ph: 'E', name: 'TaskB', ts: baseTs + 55e3}, + ]; + + traceEvents.forEach(evt => Object.assign(evt, {cat: 'devtools.timeline', args})); + + const tasks = await computedArtifacts.requestMainThreadTasks({traceEvents}); + assert.equal(tasks.length, 3); + + const taskA = tasks.find(task => task.event.name === 'TaskA'); + const taskB = tasks.find(task => task.event.name === 'TaskB'); + const taskC = tasks.find(task => task.event.name === 'TaskC'); + assert.deepStrictEqual(taskA, { + parent: undefined, + attributableURLs: [], + + children: [taskB], + event: traceEvents[2], + startTime: 0, + endTime: 100, + duration: 100, + selfTime: 50, + group: taskGroups.other, + }); + + assert.deepStrictEqual(taskB, { + parent: taskA, + attributableURLs: [], + + children: [taskC], + event: traceEvents[3], + startTime: 5, + endTime: 55, + duration: 50, + selfTime: 20, + group: taskGroups.other, + }); + }); + + it('should compute attributableURLs correctly', async () => { + const baseTs = 1241250325; + const url = s => ({args: {data: {url: s}}}); + const stackFrames = f => ({args: {data: {stackTrace: f.map(url => ({url}))}}}); + + /* + An artistic rendering of the below trace: + █████████████████████████████TaskA██████████████████████████████████████████████ + ████████████████TaskB███████████████████ + ████EvaluateScript██████ + █D█ + */ + const traceEvents = [ + ...boilerplateTrace, + {ph: 'X', name: 'TaskA', ts: baseTs, dur: 100e3, ...url('about:blank')}, + {ph: 'B', name: 'TaskB', ts: baseTs + 5e3, ...stackFrames(['urlB.1', 'urlB.2'])}, + {ph: 'X', name: 'EvaluateScript', ts: baseTs + 10e3, dur: 30e3, ...url('urlC')}, + {ph: 'X', name: 'TaskD', ts: baseTs + 15e3, dur: 5e3, ...stackFrames(['urlD'])}, + {ph: 'E', name: 'TaskB', ts: baseTs + 55e3}, + ]; + + traceEvents.forEach(evt => { + evt.cat = 'devtools.timeline'; + evt.args = evt.args || args; + }); + + const tasks = await computedArtifacts.requestMainThreadTasks({traceEvents}); + const taskA = tasks.find(task => task.event.name === 'TaskA'); + const taskB = tasks.find(task => task.event.name === 'TaskB'); + const taskC = tasks.find(task => task.event.name === 'EvaluateScript'); + const taskD = tasks.find(task => task.event.name === 'TaskD'); + + assert.deepStrictEqual(taskA.attributableURLs, []); + assert.deepStrictEqual(taskB.attributableURLs, ['urlB.1', 'urlB.2']); + assert.deepStrictEqual(taskC.attributableURLs, ['urlB.1', 'urlB.2', 'urlC']); + assert.deepStrictEqual(taskD.attributableURLs, ['urlB.1', 'urlB.2', 'urlC', 'urlD']); + }); +}); diff --git a/lighthouse-core/test/lib/traces/devtools-timeline-model-test.js b/lighthouse-core/test/lib/traces/devtools-timeline-model-test.js deleted file mode 100644 index 0a729a13dad3..000000000000 --- a/lighthouse-core/test/lib/traces/devtools-timeline-model-test.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -/* eslint-env mocha */ -const fs = require('fs'); -const assert = require('assert'); -const TimelineModel = require('../../../lib/traces/devtools-timeline-model'); - -const filename = 'devtools-homepage-w-screenshots-trace.json'; -const events = JSON.parse(fs.readFileSync(__dirname + '/../../fixtures/traces/' + filename)); -let model; - -describe('DevTools Timeline Model', function() { - it('doesn\'t throw an exception', () => { - assert.doesNotThrow(_ => { - model = new TimelineModel(events); - }); - }); - - // We're not sandboxing so we don't expect conflicts of multiple instances - // So this test is somewhat unneccessary, but we'll keep it for the good of the order - it.skip('Multiple instances don\'t conflict', () => { - let model1; - let model2; - assert.doesNotThrow(_ => { - model1 = new TimelineModel(events); - model2 = new TimelineModel(events); - }); - const events1 = model1.timelineModel().mainThreadEvents().length; - const events2 = model2.timelineModel().mainThreadEvents().length; - assert.equal(events1, events2); - }); - - it('metrics returned are expected', () => { - assert.equal(model.timelineModel().mainThreadEvents().length, 7756); - assert.equal(model.interactionModel().interactionRecords().length, 0); - assert.equal(model.frameModel().frames().length, 16); - }); - - it('top-down profile', () => { - const leavesCount = model.topDown().children.size; - assert.equal(leavesCount, 27); - const time = model.topDown().totalTime.toFixed(2); - assert.equal(time, '555.01'); - }); - - it('bottom-up profile', () => { - const leavesCount = model.bottomUp().children.size; - assert.equal(leavesCount, 242); - const topCosts = [...model.bottomUpGroupBy('URL').children.values()]; - const time = topCosts[1].totalTime.toFixed(2); - const url = topCosts[1].id; - assert.equal(time, '76.26'); - assert.equal(url, 'https://s.ytimg.com/yts/jsbin/www-embed-lightweight-vflu_2b1k/www-embed-lightweight.js'); - }); - - it('bottom-up profile - group by eventname', () => { - const bottomUpByName = model.bottomUpGroupBy('EventName'); - const leavesCount = bottomUpByName.children.size; - assert.equal(leavesCount, 13); - const topCosts = [...bottomUpByName.children.values()]; - const time = topCosts[0].selfTime.toFixed(2); - const name = topCosts[0].id; - assert.equal(time, '187.75'); - assert.equal(name, 'Layout'); - }); -}); diff --git a/lighthouse-core/test/lib/traces/tracing-processor-test.js b/lighthouse-core/test/lib/traces/tracing-processor-test.js index 3446ea156de5..1827ec34a47d 100644 --- a/lighthouse-core/test/lib/traces/tracing-processor-test.js +++ b/lighthouse-core/test/lib/traces/tracing-processor-test.js @@ -202,6 +202,9 @@ describe('TracingProcessor lib', () => { assert.equal(durations.filter(dur => isNaN(dur)).length, 0, 'NaN found'); assert.equal(durations.length, 645); + const sum = durations.reduce((a, b) => a + b); + assert.equal(Math.round(sum), 386); + assert.equal(getDurationFromIndex(50), 0.01); assert.equal(getDurationFromIndex(300), 0.04); assert.equal(getDurationFromIndex(400), 0.07); diff --git a/lighthouse-core/test/lib/web-inspector-test.js b/lighthouse-core/test/lib/web-inspector-test.js index 36cba612d77e..46761aaeecb1 100644 --- a/lighthouse-core/test/lib/web-inspector-test.js +++ b/lighthouse-core/test/lib/web-inspector-test.js @@ -12,11 +12,6 @@ const assert = require('assert'); describe('Web Inspector lib', function() { it('WebInspector exported is the real one', () => { assert.equal(typeof WebInspector, 'object'); - assert.ok(WebInspector.TimelineModel); - assert.ok(WebInspector.TimelineUIUtils); - assert.ok(WebInspector.FilmStripModel); - assert.ok(WebInspector.TimelineProfileTree); - assert.ok(WebInspector.TimelineAggregator); assert.ok(WebInspector.Color); assert.ok(WebInspector.CSSMetadata); }); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 126a12b19d3f..cbc7b5a1335d 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -652,103 +652,63 @@ "id": "mainthread-work-breakdown", "title": "Minimizes main thread work", "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.", - "score": 0.98, + "score": 0.96, "scoreDisplayMode": "numeric", - "rawValue": 1359.7759999930859, + "rawValue": 1548.5690000000002, "displayValue": [ "%10d ms", - 1359.7759999930859 + 1548.5690000000002 ], "details": { "type": "table", "headings": [ { - "key": "group", + "key": "groupLabel", "itemType": "text", "text": "Category" }, - { - "key": "category", - "itemType": "text", - "text": "Work" - }, { "key": "duration", "itemType": "ms", "granularity": 1, - "text": "Time spent" + "text": "Time Spent" } ], "items": [ { - "category": "Evaluate Script", - "group": "Script Evaluation", - "duration": 1121.2470000088215 - }, - { - "category": "Run Microtasks", - "group": "Script Evaluation", - "duration": 6.680999994277954 - }, - { - "category": "XHR Ready State Change", - "group": "Script Evaluation", - "duration": 0.026999980211257935 - }, - { - "category": "XHR Load", - "group": "Script Evaluation", - "duration": 0.011000007390975952 - }, - { - "category": "Layout", - "group": "Style & Layout", - "duration": 89.00499999523163 - }, - { - "category": "Recalculate Style", - "group": "Style & Layout", - "duration": 32.57899996638298 + "group": "scriptEvaluation", + "groupLabel": "Script Evaluation", + "duration": 1148.6240000000003 }, { - "category": "Parse HTML", - "group": "Parsing HTML & CSS", - "duration": 63.04200002551079 + "group": "other", + "groupLabel": "Other", + "duration": 177.20099999999977 }, { - "category": "Parse Stylesheet", - "group": "Parsing HTML & CSS", - "duration": 1.3200000524520874 + "group": "styleLayout", + "groupLabel": "Style & Layout", + "duration": 121.584 }, { - "category": "Minor GC", - "group": "Garbage collection", - "duration": 19.94599997997284 + "group": "parseHTML", + "groupLabel": "Parse HTML & CSS", + "duration": 53.65500000000001 }, { - "category": "DOM GC", - "group": "Garbage collection", - "duration": 6.252999991178513 + "group": "garbageCollection", + "groupLabel": "Garbage Collection", + "duration": 26.198999999999998 }, { - "category": "Compile Script", - "group": "Script Parsing & Compile", - "duration": 7.7519999742507935 + "group": "paintCompositeRender", + "groupLabel": "Rendering", + "duration": 13.486999999999998 }, { - "category": "Update Layer Tree", - "group": "Compositing", - "duration": 4.238999992609024 - }, - { - "category": "Composite Layers", - "group": "Compositing", - "duration": 2.732000023126602 - }, - { - "category": "Paint", - "group": "Paint", - "duration": 4.94200000166893 + "group": "scriptParseCompile", + "groupLabel": "Script Parsing & Compilation", + "duration": 7.818999999999999 } ] } @@ -757,12 +717,12 @@ "id": "bootup-time", "title": "JavaScript boot-up time", "description": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/bootup).", - "score": 0.9, + "score": 0.92, "scoreDisplayMode": "numeric", - "rawValue": 1302.3579999804497, + "rawValue": 1150.932, "displayValue": [ "%10d ms", - 1302.3579999804497 + 1150.932 ], "details": { "type": "table", @@ -772,6 +732,12 @@ "itemType": "url", "text": "URL" }, + { + "key": "total", + "granularity": 1, + "itemType": "ms", + "text": "Total" + }, { "key": "scripting", "granularity": 1, @@ -782,31 +748,31 @@ "key": "scriptParseCompile", "granularity": 1, "itemType": "ms", - "text": "Script Parsing & Compile" + "text": "Script Parsing & Compilation" } ], "items": [ { "url": "http://localhost:10200/dobetterweb/dbw_tester.html", - "sum": 957.277999997139, - "scripting": 954.5010000169277, - "scriptParseCompile": 2.776999980211258 + "total": 966.9800000000001, + "scripting": 957.494, + "scriptParseCompile": 2.777 }, { - "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", - "sum": 81.23699998855591, - "scripting": 80.03200000524521, - "scriptParseCompile": 1.2049999833106995 + "url": "http://localhost:10200/zone.js", + "total": 101.21099999999998, + "scripting": 89.50799999999998, + "scriptParseCompile": 1.674 }, { - "url": "http://localhost:10200/zone.js", - "sum": 78.23800000548363, - "scripting": 76.56400001049042, - "scriptParseCompile": 1.6739999949932098 + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "total": 82.741, + "scripting": 80.032, + "scriptParseCompile": 1.205 } ], "summary": { - "wastedMs": 1302.3579999804497 + "wastedMs": 1150.932 } } }, diff --git a/lighthouse-extension/dtm-transform.js b/lighthouse-extension/dtm-transform.js deleted file mode 100644 index c62209be0c8a..000000000000 --- a/lighthouse-extension/dtm-transform.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license Copyright 2016 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -'use strict'; - -const through = require('through2'); -const path = require('path'); - -/** - * This is a browserify transform that looks for requires to devtools-timeline-model - * and replaces them for the local version in Lighthouse. This is just for the extension - * since by default it doesn't browserify properly. - */ -module.exports = function() { - const fileContents = []; - return through(function(part, enc, next) { - fileContents.push(part); - next(); - }, function(done) { - let fileContentsString = fileContents.join(''); - const dtmRegExp = /require\(['"]devtools-timeline-model['"]\)/gim; - const newPath = path.join(__dirname, '../', - 'lighthouse-core/lib/traces/devtools-timeline-model'); - - if (dtmRegExp.test(fileContentsString)) { - fileContentsString = fileContentsString.replace(dtmRegExp, `require("${newPath}")`); - } - - // eslint-disable-next-line no-invalid-this - this.push(fileContentsString); - done(); - }); -}; diff --git a/lighthouse-extension/gulpfile.js b/lighthouse-extension/gulpfile.js index 6a7b7f172145..646f6c610dc2 100644 --- a/lighthouse-extension/gulpfile.js +++ b/lighthouse-extension/gulpfile.js @@ -123,11 +123,7 @@ gulp.task('browserify-lighthouse', () => { bundle = applyBrowserifyTransforms(bundle); // scripts will need some additional transforms, ignores and requires… - - // Do the additional transform to convert references of devtools-timeline-model - // to the modified version internal to Lighthouse. - bundle.transform('./dtm-transform.js', {global: true}) - .ignore('source-map') + bundle.ignore('source-map') .ignore('debug/node') .ignore('raven') .ignore('mkdirp') diff --git a/typings/artifacts.d.ts b/typings/artifacts.d.ts index b3d0beaf47bb..8844120642d2 100644 --- a/typings/artifacts.d.ts +++ b/typings/artifacts.d.ts @@ -8,6 +8,8 @@ import parseManifest = require('../lighthouse-core/lib/manifest-parser.js'); import _LanternSimulator = require('../lighthouse-core/lib/dependency-graph/simulator/simulator.js'); import speedline = require('speedline'); +type _TaskNode = import('../lighthouse-core/gather/computed/main-thread-tasks').TaskNode; + type LanternSimulator = InstanceType; declare global { @@ -113,7 +115,6 @@ declare global { export interface ComputedArtifacts { requestCriticalRequestChains(data: {devtoolsLog: DevtoolsLog, URL: Artifacts['URL']}): Promise; - requestDevtoolsTimelineModel(trace: Trace): Promise; requestLoadSimulator(data: {devtoolsLog: DevtoolsLog, settings: Config.Settings}): Promise; requestMainResource(data: {devtoolsLog: DevtoolsLog, URL: Artifacts['URL']}): Promise; requestManifestValues(manifest: LH.Artifacts['Manifest']): Promise; @@ -122,6 +123,7 @@ declare global { requestNetworkRecords(devtoolsLog: DevtoolsLog): Promise; requestPageDependencyGraph(data: {trace: Trace, devtoolsLog: DevtoolsLog}): Promise; requestPushedRequests(devtoolsLogs: DevtoolsLog): Promise; + requestMainThreadTasks(trace: Trace): Promise; requestTraceOfTab(trace: Trace): Promise; requestScreenshots(trace: Trace): Promise<{timestamp: number, datauri: string}[]>; requestSpeedline(trace: Trace): Promise; @@ -144,6 +146,8 @@ declare global { } module Artifacts { + export type TaskNode = _TaskNode; + export interface Accessibility { violations: { id: string; @@ -303,27 +307,6 @@ declare global { } } - export interface DevtoolsTimelineFilmStripModel { - frames(): Array<{ - imageDataPromise(): Promise; - timestamp: number; - }>; - } - - export interface DevtoolsTimelineModelNode { - children: Map; - selfTime: number; - // SDK.TracingModel.Event - event: { - name: string; - }; - } - - export interface DevtoolsTimelineModel { - filmStripModel(): Artifacts.DevtoolsTimelineFilmStripModel; - bottomUpGroupBy(grouping: string): DevtoolsTimelineModelNode; - } - export type ManifestValueCheckID = 'hasStartUrl'|'hasIconsAtLeast192px'|'hasIconsAtLeast512px'|'hasPWADisplayValue'|'hasBackgroundColor'|'hasThemeColor'|'hasShortName'|'hasName'|'shortNameLength'; export interface ManifestValues { diff --git a/typings/externs.d.ts b/typings/externs.d.ts index 47237948735e..8e04d0dbfab8 100644 --- a/typings/externs.d.ts +++ b/typings/externs.d.ts @@ -141,6 +141,8 @@ declare global { name: string; cat: string; args: { + fileName?: string; + snapshot?: string; data?: { frames?: { frame: string;