From b11d3d65c58b0e9b68fe28e673f06cb4e20559f1 Mon Sep 17 00:00:00 2001 From: Patrick Hulce Date: Thu, 18 Apr 2019 15:47:32 -0500 Subject: [PATCH] core(driver): enable async stacks (#5504) --- .../computed/page-dependency-graph.js | 20 ++++++++++++--- lighthouse-core/gather/driver.js | 15 ++++++++--- lighthouse-core/gather/gather-runner.js | 3 ++- .../computed/page-dependency-graph-test.js | 25 +++++++++++++++++++ lighthouse-core/test/gather/fake-driver.js | 3 +++ .../test/gather/gather-runner-test.js | 5 ++++ 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/lighthouse-core/computed/page-dependency-graph.js b/lighthouse-core/computed/page-dependency-graph.js index 2a9a9344268c..292eafbd8732 100644 --- a/lighthouse-core/computed/page-dependency-graph.js +++ b/lighthouse-core/computed/page-dependency-graph.js @@ -30,9 +30,23 @@ class PageDependencyGraph { static getNetworkInitiators(record) { if (!record.initiator) return []; if (record.initiator.url) return [record.initiator.url]; - if (record.initiator.type === 'script' && record.initiator.stack) { - const frames = record.initiator.stack.callFrames; - return Array.from(new Set(frames.map(frame => frame.url))).filter(Boolean); + if (record.initiator.type === 'script') { + // Script initiators have the stack of callFrames from all functions that led to this request. + // If async stacks are enabled, then the stack will also have the parent functions that asynchronously + // led to this request chained in the `parent` property. + /** @type {Set} */ + const scriptURLs = new Set(); + let stack = record.initiator.stack; + while (stack) { + const callFrames = stack.callFrames || []; + for (const frame of callFrames) { + if (frame.url) scriptURLs.add(frame.url); + } + + stack = stack.parent; + } + + return Array.from(scriptURLs); } return []; diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js index 61e97ff8ea0e..29b6679bcd9a 100644 --- a/lighthouse-core/gather/driver.js +++ b/lighthouse-core/gather/driver.js @@ -1337,9 +1337,6 @@ class Driver { const uniqueCategories = Array.from(new Set(traceCategories)); // Check any domains that could interfere with or add overhead to the trace. - if (this.isDomainEnabled('Debugger')) { - throw new Error('Debugger domain enabled when starting trace'); - } if (this.isDomainEnabled('CSS')) { throw new Error('CSS domain enabled when starting trace'); } @@ -1405,6 +1402,18 @@ class Driver { return this.sendCommand('Runtime.enable'); } + /** + * Enables `Debugger` domain to receive async stacktrace information on network request initiators. + * This is critical for tracing certain performance simulation situations. + * + * @return {Promise} + */ + async enableAsyncStacks() { + await this.sendCommand('Debugger.enable'); + await this.sendCommand('Debugger.setSkipAllPauses', {skip: true}); + await this.sendCommand('Debugger.setAsyncCallStackDepth', {maxDepth: 8}); + } + /** * @param {LH.Config.Settings} settings * @return {Promise} diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index 033a9a8bf317..f7efa385fa02 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -33,7 +33,7 @@ const Driver = require('../gather/driver.js'); // eslint-disable-line no-unused- * i. assertNoSameOriginServiceWorkerClients * ii. retrieve and save userAgent * iii. beginEmulation - * iv. enableRuntimeEvents + * iv. enableRuntimeEvents/enableAsyncStacks * v. evaluateScriptOnLoad rescue native Promise from potential polyfill * vi. register a performance observer * vii. register dialog dismisser @@ -109,6 +109,7 @@ class GatherRunner { await driver.assertNoSameOriginServiceWorkerClients(options.requestedUrl); await driver.beginEmulation(options.settings); await driver.enableRuntimeEvents(); + await driver.enableAsyncStacks(); await driver.cacheNatives(); await driver.registerPerformanceObserver(); await driver.dismissJavaScriptDialogs(); diff --git a/lighthouse-core/test/computed/page-dependency-graph-test.js b/lighthouse-core/test/computed/page-dependency-graph-test.js index 814378405d55..97712ed01ae2 100644 --- a/lighthouse-core/test/computed/page-dependency-graph-test.js +++ b/lighthouse-core/test/computed/page-dependency-graph-test.js @@ -281,6 +281,31 @@ describe('PageDependencyGraph computed artifact:', () => { assert.equal(nodes[2].isMainDocument(), false); }); + it('should link up script initiators', () => { + const request1 = createRequest(1, '1', 0); + const request2 = createRequest(2, '2', 5); + const request3 = createRequest(3, '3', 5); + const request4 = createRequest(4, '4', 20); + request4.initiator = { + type: 'script', + stack: {callFrames: [{url: '2'}], parent: {parent: {callFrames: [{url: '3'}]}}}, + }; + const networkRecords = [request1, request2, request3, request4]; + + addTaskEvents(0, 0, []); + + const graph = PageDependencyGraph.createGraph(traceOfTab, networkRecords); + const nodes = []; + graph.traverse(node => nodes.push(node)); + + assert.equal(nodes.length, 4); + assert.deepEqual(nodes.map(node => node.id), [1, 2, 3, 4]); + assert.deepEqual(nodes[0].getDependencies(), []); + assert.deepEqual(nodes[1].getDependencies(), [nodes[0]]); + assert.deepEqual(nodes[2].getDependencies(), [nodes[0]]); + assert.deepEqual(nodes[3].getDependencies(), [nodes[1], nodes[2]]); + }); + it('should throw when root node is not related to main document', () => { const request1 = createRequest(1, '1', 0, null, NetworkRequest.TYPES.Other); const request2 = createRequest(2, '2', 5, null, NetworkRequest.TYPES.Document); diff --git a/lighthouse-core/test/gather/fake-driver.js b/lighthouse-core/test/gather/fake-driver.js index 73e0782e0a4a..1db517772bea 100644 --- a/lighthouse-core/test/gather/fake-driver.js +++ b/lighthouse-core/test/gather/fake-driver.js @@ -43,6 +43,9 @@ function makeFakeDriver({protocolGetVersionResponse}) { enableRuntimeEvents() { return Promise.resolve(); }, + enableAsyncStacks() { + return Promise.resolve(); + }, evaluateScriptOnLoad() { return Promise.resolve(); }, diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 22c5762e497f..36ab4f962d67 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -45,6 +45,9 @@ function getMockedEmulationDriver(emulationFn, netThrottleFn, cpuThrottleFn, enableRuntimeEvents() { return Promise.resolve(); } + enableAsyncStacks() { + return Promise.resolve(); + } assertNoSameOriginServiceWorkerClients() { return Promise.resolve(); } @@ -333,6 +336,7 @@ describe('GatherRunner', function() { setThrottling: asyncFunc, dismissJavaScriptDialogs: asyncFunc, enableRuntimeEvents: asyncFunc, + enableAsyncStacks: asyncFunc, cacheNatives: asyncFunc, gotoURL: asyncFunc, registerPerformanceObserver: asyncFunc, @@ -392,6 +396,7 @@ describe('GatherRunner', function() { setThrottling: asyncFunc, dismissJavaScriptDialogs: asyncFunc, enableRuntimeEvents: asyncFunc, + enableAsyncStacks: asyncFunc, cacheNatives: asyncFunc, gotoURL: asyncFunc, registerPerformanceObserver: asyncFunc,