chrome-devtools-frontend
Version:
Chrome DevTools UI
200 lines (168 loc) • 8.46 kB
text/typescript
// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Lantern from '../lantern.js';
const {ConnectionPool} = Lantern.Simulation;
describe('ConnectionPool', () => {
const rtt = 100;
const throughput = 10000 * 1024;
let requestId: number;
function request(data: Partial<Lantern.Types.NetworkRequest> = {}): Lantern.Types.NetworkRequest {
const url = data.url || 'http://example.com';
const origin = new URL(url).origin;
const scheme = url.split(':')[0];
const host = new URL(url).hostname;
return {
requestId: String(requestId++),
url,
protocol: 'http/1.1',
parsedURL: {scheme, securityOrigin: origin, host},
...data,
} as Lantern.Types.NetworkRequest;
}
function simulationOptions(options: {
rtt?: number,
throughput?: number,
additionalRttByOrigin?: Map<string, number>,
serverResponseTimeByOrigin?: Map<string, number>,
}): Required<Lantern.Types.Simulation.Options> {
const defaults: Required<Lantern.Types.Simulation.Options> = {
rtt: 150,
throughput: 1024,
additionalRttByOrigin: new Map(),
serverResponseTimeByOrigin: new Map(),
// These options are not used by ConnectionPool, but are required in the types.
cpuSlowdownMultiplier: 1,
layoutTaskMultiplier: 1,
observedThroughput: 1,
maximumConcurrentRequests: 8,
};
return Object.assign(defaults, options);
}
beforeEach(() => {
requestId = 1;
});
describe('#constructor', () => {
it('should create the pool', () => {
const pool = new ConnectionPool([request()], simulationOptions({rtt, throughput}));
// Make sure 6 connections are created for each origin
assert.lengthOf(pool.connectionsByOrigin.get('http://example.com') ?? [], 6);
// Make sure it populates connectionWasReused
assert.isFalse(pool.connectionReusedByRequestId.get('1'));
const connection = pool.connectionsByOrigin.get('http://example.com')?.[0];
assert.isOk(connection);
assert.strictEqual(connection.rtt, rtt);
assert.strictEqual(connection.throughput, throughput);
assert.strictEqual(connection.serverLatency, 30); // sets to default value
});
it('should set TLS properly', () => {
const recordA = request({url: 'https://example.com'});
const pool = new ConnectionPool([recordA], simulationOptions({rtt, throughput}));
const connection = pool.connectionsByOrigin.get('https://example.com')?.[0];
assert.isOk(connection?.ssl, 'should have set connection TLS');
});
it('should set H2 properly', () => {
const recordA = request({protocol: 'h2'});
const pool = new ConnectionPool([recordA], simulationOptions({rtt, throughput}));
const connection = pool.connectionsByOrigin.get('http://example.com')?.[0];
assert.isOk(connection?.isH2(), 'should have set HTTP/2');
assert.lengthOf(pool.connectionsByOrigin.get('http://example.com') ?? [], 1);
});
it('should set origin-specific RTT properly', () => {
const additionalRttByOrigin = new Map([['http://example.com', 63]]);
const pool = new ConnectionPool([request()], simulationOptions({rtt, throughput, additionalRttByOrigin}));
const connection = pool.connectionsByOrigin.get('http://example.com')?.[0];
assert.isOk(connection);
assert.strictEqual(connection.rtt, rtt + 63);
});
it('should set origin-specific server latency properly', () => {
const serverResponseTimeByOrigin = new Map([['http://example.com', 63]]);
const pool = new ConnectionPool([request()], simulationOptions({rtt, throughput, serverResponseTimeByOrigin}));
const connection = pool.connectionsByOrigin.get('http://example.com')?.[0];
assert.isOk(connection);
assert.strictEqual(connection.serverLatency, 63);
});
});
describe('.acquire', () => {
it('should remember the connection associated with each request', () => {
const requestA = request();
const requestB = request();
const pool = new ConnectionPool([requestA, requestB], simulationOptions({rtt, throughput}));
const connectionForA = pool.acquire(requestA);
const connectionForB = pool.acquire(requestB);
for (let i = 0; i < 10; i++) {
assert.strictEqual(pool.acquireActiveConnectionFromRequest(requestA), connectionForA);
assert.strictEqual(pool.acquireActiveConnectionFromRequest(requestB), connectionForB);
}
assert.deepEqual(pool.connectionsInUse(), [connectionForA, connectionForB]);
});
it('should allocate at least 6 connections', () => {
const pool = new ConnectionPool([request()], simulationOptions({rtt, throughput}));
for (let i = 0; i < 6; i++) {
assert.isOk(pool.acquire(request()), `did not find connection for ${i}th request`);
}
});
it('should allocate all connections', () => {
const records = new Array(7).fill(undefined, 0, 7).map(() => request());
const pool = new ConnectionPool(records, simulationOptions({rtt, throughput}));
const connections = records.map(request => pool.acquire(request));
assert.isOk(connections[0], 'did not find connection for 1st request');
assert.isOk(connections[5], 'did not find connection for 6th request');
assert.isOk(connections[6], 'did not find connection for 7th request');
});
it('should be oblivious to connection reuse', () => {
const coldRecord = request();
const warmRecord = request();
const pool = new ConnectionPool([coldRecord, warmRecord], simulationOptions({rtt, throughput}));
pool.connectionReusedByRequestId.set(warmRecord.requestId, true);
assert.isOk(pool.acquire(coldRecord), 'should have acquired connection');
assert.isOk(pool.acquire(warmRecord), 'should have acquired connection');
pool.release(coldRecord);
for (const connection of pool.connectionsByOrigin.get('http://example.com') ?? []) {
connection.setWarmed(true);
}
assert.isOk(pool.acquire(coldRecord), 'should have acquired connection');
assert.isOk(pool.acquireActiveConnectionFromRequest(warmRecord), 'should have acquired connection');
});
it('should acquire in order of warmness', () => {
const recordA = request();
const recordB = request();
const recordC = request();
const pool = new ConnectionPool([recordA, recordB, recordC], simulationOptions({rtt, throughput}));
pool.connectionReusedByRequestId.set(recordA.requestId, true);
pool.connectionReusedByRequestId.set(recordB.requestId, true);
pool.connectionReusedByRequestId.set(recordC.requestId, true);
const connections = pool.connectionsByOrigin.get('http://example.com');
assert.isOk(connections);
const [connectionWarm, connectionWarmer, connectionWarmest] = connections;
connectionWarm.setWarmed(true);
connectionWarm.setCongestionWindow(10);
connectionWarmer.setWarmed(true);
connectionWarmer.setCongestionWindow(100);
connectionWarmest.setWarmed(true);
connectionWarmest.setCongestionWindow(1000);
assert.strictEqual(pool.acquire(recordA), connectionWarmest);
assert.strictEqual(pool.acquire(recordB), connectionWarmer);
assert.strictEqual(pool.acquire(recordC), connectionWarm);
});
});
describe('.release', () => {
it('noop for request without connection', () => {
const requestA = request();
const pool = new ConnectionPool([requestA], simulationOptions({rtt, throughput}));
assert.isUndefined(pool.release(requestA));
});
it('frees the connection for reissue', () => {
const requests = new Array(6).fill(undefined, 0, 7).map(() => request());
const pool = new ConnectionPool(requests, simulationOptions({rtt, throughput}));
requests.push(request());
requests.forEach(request => pool.acquire(request));
assert.lengthOf(pool.connectionsInUse(), 6);
assert.isNotOk(pool.acquire(requests[6]), 'had connection that is in use');
pool.release(requests[0]);
assert.lengthOf(pool.connectionsInUse(), 5);
assert.isOk(pool.acquire(requests[6]), 'could not reissue released connection');
assert.isNotOk(pool.acquire(requests[0]), 'had connection that is in use');
});
});
});