Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core(simulator): add DNS timing #5607

Merged
merged 4 commits into from
Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class FirstContentfulPaint extends MetricArtifact {
*/
get COEFFICIENTS() {
return {
intercept: 600,
optimistic: 0.6,
intercept: 0,
optimistic: 0.5,
pessimistic: 0.5,
};
}
Expand Down
12 changes: 12 additions & 0 deletions lighthouse-core/gather/computed/metrics/lantern-first-cpu-idle.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ class LanternFirstCPUIdle extends LanternInteractive {
return 'LanternFirstCPUIdle';
}

/**
* @return {LH.Gatherer.Simulation.MetricCoefficients}
*/
get COEFFICIENTS() {
return {
intercept: 0,
optimistic: 1,
pessimistic: 0,
};
}


/**
* @param {LH.Gatherer.Simulation.Result} simulation
* @param {Object} extras
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ class FirstMeaningfulPaint extends MetricArtifact {
*/
get COEFFICIENTS() {
return {
intercept: 900,
optimistic: 0.45,
pessimistic: 0.6,
intercept: 0,
optimistic: 0.5,
pessimistic: 0.5,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class Interactive extends MetricArtifact {
*/
get COEFFICIENTS() {
return {
intercept: 1600,
optimistic: 0.6,
pessimistic: 0.45,
intercept: 0,
optimistic: 0.5,
pessimistic: 0.5,
};
}

Expand Down
83 changes: 83 additions & 0 deletions lighthouse-core/lib/dependency-graph/simulator/dns-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @license Copyright 2018 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';

// A DNS lookup will usually take ~1-2 roundtrips of connection latency plus the extra DNS routing time.
// Example: https://www.webpagetest.org/result/180703_3A_e33ec79747c002ed4d7bcbfc81462203/1/details/#waterfall_view_step1
// Example: https://www.webpagetest.org/result/180707_1M_89673eb633b5d98386de95dfcf9b33d5/1/details/#waterfall_view_step1
// DNS is highly variable though, many times it's a little more than 1, but can easily be 4-5x RTT.
// We'll use 2 since it seems to give the most accurate results on average, but this can be tweaked.
const DNS_RESOLUTION_RTT_MULTIPLIER = 2;

class DNSCache {
/**
* @param {{rtt: number}} options
*/
constructor(options) {
this._options = Object.assign(
{
rtt: undefined,
},
options
);

if (!this._options.rtt) {
throw new Error('Cannot create DNS cache with no rtt');
}

this._rtt = this._options.rtt;
/** @type {Map<string, {resolvedAt: number}>} */
this._resolvedDomainNames = new Map();
}

/**
* @param {LH.Artifacts.NetworkRequest} request
* @param {{requestedAt: number, shouldUpdateCache: boolean}=} options
* @return {number}
*/
getTimeUntilResolution(request, options) {
const {requestedAt = 0, shouldUpdateCache = false} = options || {};

const domain = request.parsedURL.host;
const cacheEntry = this._resolvedDomainNames.get(domain);
let timeUntilResolved = this._rtt * DNSCache.RTT_MULTIPLIER;
if (cacheEntry) {
const timeUntilCachedIsResolved = Math.max(cacheEntry.resolvedAt - requestedAt, 0);
timeUntilResolved = Math.min(timeUntilCachedIsResolved, timeUntilResolved);
}

const resolvedAt = requestedAt + timeUntilResolved;
if (shouldUpdateCache) this._updateCacheResolvedAtIfNeeded(request, resolvedAt);

return timeUntilResolved;
}

/**
* @param {LH.Artifacts.NetworkRequest} request
* @param {number} resolvedAt
*/
_updateCacheResolvedAtIfNeeded(request, resolvedAt) {
const domain = request.parsedURL.host;
const cacheEntry = this._resolvedDomainNames.get(domain) || {resolvedAt};
cacheEntry.resolvedAt = Math.min(cacheEntry.resolvedAt, resolvedAt);
this._resolvedDomainNames.set(domain, cacheEntry);
}

/**
* Forcefully sets the DNS resolution time for a record.
* Useful for testing and alternate execution simulations.
*
* @param {string} domain
* @param {number} resolvedAt
*/
setResolvedAt(domain, resolvedAt) {
this._resolvedDomainNames.set(domain, {resolvedAt});
}
}

DNSCache.RTT_MULTIPLIER = DNS_RESOLUTION_RTT_MULTIPLIER;

module.exports = DNSCache;
24 changes: 19 additions & 5 deletions lighthouse-core/lib/dependency-graph/simulator/simulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const BaseNode = require('../base-node');
const TcpConnection = require('./tcp-connection');
const ConnectionPool = require('./connection-pool');
const DNSCache = require('./dns-cache');
const mobile3G = require('../../../config/constants').throttling.mobile3G;

/** @typedef {BaseNode.Node} Node */
Expand Down Expand Up @@ -66,6 +67,7 @@ class Simulator {
/** @type {Map<string, number>} */
this._numberInProgressByType = new Map();
this._nodes = {};
this._dns = new DNSCache({rtt: this._rtt});
// @ts-ignore
this._connectionPool = /** @type {ConnectionPool} */ (null);
}
Expand Down Expand Up @@ -260,20 +262,26 @@ class Simulator {
* @return {number}
*/
_estimateNetworkTimeRemaining(networkNode) {
const record = networkNode.record;
const timingData = this._getTimingData(networkNode);

let timeElapsed = 0;
if (networkNode.fromDiskCache) {
// Rough access time for seeking to location on disk and reading sequentially
// Rough access time for seeking to location on disk and reading sequentially = 8ms + 20ms/MB
// @see http://norvig.com/21-days.html#answers
const sizeInMb = (networkNode.record.resourceSize || 0) / 1024 / 1024;
const sizeInMb = (record.resourceSize || 0) / 1024 / 1024;
timeElapsed = 8 + 20 * sizeInMb - timingData.timeElapsed;
} else {
// If we're estimating time remaining, we already acquired a connection for this record, definitely non-null
const connection = /** @type {TcpConnection} */ (this._acquireConnection(networkNode.record));
const connection = /** @type {TcpConnection} */ (this._acquireConnection(record));
const dnsResolutionTime = this._dns.getTimeUntilResolution(record, {
requestedAt: timingData.startTime,
shouldUpdateCache: true,
});
const timeAlreadyElapsed = timingData.timeElapsed;
const calculation = connection.simulateDownloadUntil(
networkNode.record.transferSize - timingData.bytesDownloaded,
{timeAlreadyElapsed: timingData.timeElapsed, maximumTimeToElapse: Infinity}
record.transferSize - timingData.bytesDownloaded,
{timeAlreadyElapsed, dnsResolutionTime, maximumTimeToElapse: Infinity}
);

timeElapsed = calculation.timeElapsed;
Expand Down Expand Up @@ -318,9 +326,14 @@ class Simulator {
const record = node.record;
// If we're updating the progress, we already acquired a connection for this record, definitely non-null
const connection = /** @type {TcpConnection} */ (this._acquireConnection(record));
const dnsResolutionTime = this._dns.getTimeUntilResolution(record, {
requestedAt: timingData.startTime,
shouldUpdateCache: true,
});
const calculation = connection.simulateDownloadUntil(
record.transferSize - timingData.bytesDownloaded,
{
dnsResolutionTime,
timeAlreadyElapsed: timingData.timeElapsed,
maximumTimeToElapse: timePeriodLength - timingData.timeElapsedOvershoot,
}
Expand Down Expand Up @@ -386,6 +399,7 @@ class Simulator {

// initialize the necessary data containers
this._flexibleOrdering = !!options.flexibleOrdering;
this._dns = new DNSCache({rtt: this._rtt});
this._initializeConnectionPool(graph);
this._initializeAuxiliaryData();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ class TcpConnection {
* @return {DownloadResults}
*/
simulateDownloadUntil(bytesToDownload, options) {
const {timeAlreadyElapsed = 0, maximumTimeToElapse = Infinity} = options || {};
const {timeAlreadyElapsed = 0, maximumTimeToElapse = Infinity, dnsResolutionTime = 0} =
options || {};

if (this._warmed && this._h2) {
bytesToDownload -= this._h2OverflowBytesDownloaded;
Expand All @@ -133,6 +134,8 @@ class TcpConnection {
let handshakeAndRequest = oneWayLatency;
if (!this._warmed) {
handshakeAndRequest =
// DNS lookup
dnsResolutionTime +
// SYN
oneWayLatency +
// SYN ACK
Expand Down Expand Up @@ -188,6 +191,7 @@ module.exports = TcpConnection;

/**
* @typedef DownloadOptions
* @property {number} [dnsResolutionTime]
* @property {number} [timeAlreadyElapsed]
* @property {number} [maximumTimeToElapse]
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PWA: load-fast-enough-for-pwa audit overrides with simulated result when throttling is modified 1`] = `3427`;
38 changes: 38 additions & 0 deletions lighthouse-core/test/audits/__snapshots__/metrics-test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Performance: metrics evaluates valid input correctly 1`] = `
Object {
"estimatedInputLatency": 104,
"estimatedInputLatencyTs": undefined,
"firstCPUIdle": 3351,
"firstCPUIdleTs": undefined,
"firstContentfulPaint": 911,
"firstContentfulPaintTs": undefined,
"firstMeaningfulPaint": 1355,
"firstMeaningfulPaintTs": undefined,
"interactive": 3427,
"interactiveTs": undefined,
"observedDomContentLoaded": 560,
"observedDomContentLoadedTs": 225414732309,
"observedFirstContentfulPaint": 499,
"observedFirstContentfulPaintTs": 225414670885,
"observedFirstMeaningfulPaint": 783,
"observedFirstMeaningfulPaintTs": 225414955343,
"observedFirstPaint": 499,
"observedFirstPaintTs": 225414670868,
"observedFirstVisualChange": 520,
"observedFirstVisualChangeTs": 225414692015,
"observedLastVisualChange": 818,
"observedLastVisualChangeTs": 225414990015,
"observedLoad": 2199,
"observedLoadTs": 225416370913,
"observedNavigationStart": 0,
"observedNavigationStartTs": 225414172015,
"observedSpeedIndex": 605,
"observedSpeedIndexTs": 225414776724,
"observedTraceEnd": 12540,
"observedTraceEndTs": 225426711887,
"speedIndex": 1656,
"speedIndexTs": undefined,
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Performance: predictive performance audit should compute the predicted values 1`] = `
Object {
"optimisticEIL": 101,
"optimisticFCP": 911,
"optimisticFMP": 1211,
"optimisticSI": 605,
"optimisticTTFCPUI": 3351,
"optimisticTTI": 3351,
"pessimisticEIL": 158,
"pessimisticFCP": 911,
"pessimisticFMP": 1498,
"pessimisticSI": 1630,
"pessimisticTTFCPUI": 3502,
"pessimisticTTI": 3502,
"roughEstimateOfEIL": 104,
"roughEstimateOfFCP": 911,
"roughEstimateOfFMP": 1355,
"roughEstimateOfSI": 1656,
"roughEstimateOfTTFCPUI": 3351,
"roughEstimateOfTTI": 3427,
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Byte efficiency base audit should allow overriding of computeWasteWithTTIGraph 1`] = `
Object {
"default": 1330,
"justTTI": 950,
}
`;

exports[`Byte efficiency base audit should create load simulator with the specified settings 1`] = `1330`;

exports[`Byte efficiency base audit should create load simulator with the specified settings 2`] = `22950`;
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,14 @@ describe('Byte efficiency base audit', () => {
let settings = {throttlingMethod: 'simulate', throttling: modestThrottling};
let result = await MockAudit.audit(artifacts, {settings});
// expect modest savings
assert.equal(result.rawValue, 1480);
expect(result.rawValue).toBeLessThan(5000);
expect(result.rawValue).toMatchSnapshot();

settings = {throttlingMethod: 'simulate', throttling: ultraSlowThrottling};
result = await MockAudit.audit(artifacts, {settings});
// expect lots of savings
assert.equal(result.rawValue, 22350);
expect(result.rawValue).not.toBeLessThan(5000);
expect(result.rawValue).toMatchSnapshot();
});

it('should allow overriding of computeWasteWithTTIGraph', async () => {
Expand Down Expand Up @@ -251,8 +253,8 @@ describe('Byte efficiency base audit', () => {
const settings = {throttlingMethod: 'simulate', throttling: modestThrottling};
const result = await MockAudit.audit(artifacts, {settings});
const resultTti = await MockJustTTIAudit.audit(artifacts, {settings});
// expect more savings from default
assert.equal(result.rawValue, 1480);
assert.equal(resultTti.rawValue, 800);
// expect less savings with just TTI
expect(resultTti.rawValue).toBeLessThan(result.rawValue);
expect({default: result.rawValue, justTTI: resultTti.rawValue}).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('Render blocking resources audit', () => {
beforeEach(() => {
requestId = 1;
record = props => {
const parsedURL = {securityOrigin: 'http://example.com'};
const parsedURL = {host: 'example.com', securityOrigin: 'http://example.com'};
return Object.assign({parsedURL, requestId: requestId++}, props);
};
});
Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe('PWA: load-fast-enough-for-pwa audit', () => {

const settings = {throttlingMethod: 'provided', throttling: {rttMs: 40, throughput: 100000}};
const result = await FastPWAAudit.audit(artifacts, {settings});
assert.equal(Math.round(result.rawValue), 4309);
expect(result.rawValue).toBeGreaterThan(2000);
expect(Math.round(result.rawValue)).toMatchSnapshot();
});
});
Loading