chrome-devtools-frontend
Version:
Chrome DevTools UI
151 lines (122 loc) • 5.42 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 Core from '../core/core.js';
import type * as Lantern from '../types/types.js';
import {TCPConnection} from './TCPConnection.js';
const DEFAULT_SERVER_RESPONSE_TIME = 30;
const TLS_SCHEMES = ['https', 'wss'];
// Each origin can have 6 simultaneous connections open
// https://cs.chromium.org/chromium/src/net/socket/client_socket_pool_manager.cc?type=cs&q="int+g_max_sockets_per_group"
const CONNECTIONS_PER_ORIGIN = 6;
export class ConnectionPool {
options: Required<Lantern.Simulation.Options>;
records: Lantern.NetworkRequest[];
connectionsByOrigin: Map<string, TCPConnection[]>;
connectionsByRequest: Map<Lantern.NetworkRequest, TCPConnection>;
_connectionsInUse: Set<TCPConnection>;
connectionReusedByRequestId: Map<string, boolean>;
constructor(records: Lantern.NetworkRequest[], options: Required<Lantern.Simulation.Options>) {
this.options = options;
this.records = records;
this.connectionsByOrigin = new Map();
this.connectionsByRequest = new Map();
this._connectionsInUse = new Set();
this.connectionReusedByRequestId = Core.NetworkAnalyzer.estimateIfConnectionWasReused(records, {
forceCoarseEstimates: true,
});
this.initializeConnections();
}
connectionsInUse(): TCPConnection[] {
return Array.from(this._connectionsInUse);
}
initializeConnections(): void {
const connectionReused = this.connectionReusedByRequestId;
const additionalRttByOrigin = this.options.additionalRttByOrigin;
const serverResponseTimeByOrigin = this.options.serverResponseTimeByOrigin;
const recordsByOrigin = Core.NetworkAnalyzer.groupByOrigin(this.records);
for (const [origin, requests] of recordsByOrigin.entries()) {
const connections = [];
const additionalRtt = additionalRttByOrigin.get(origin) || 0;
const responseTime = serverResponseTimeByOrigin.get(origin) || DEFAULT_SERVER_RESPONSE_TIME;
for (const request of requests) {
if (connectionReused.get(request.requestId)) {
continue;
}
const isTLS = TLS_SCHEMES.includes(request.parsedURL.scheme);
const isH2 = request.protocol === 'h2';
const connection = new TCPConnection(
this.options.rtt + additionalRtt,
this.options.throughput,
responseTime,
isTLS,
isH2,
);
connections.push(connection);
}
if (!connections.length) {
throw new Core.LanternError(`Could not find a connection for origin: ${origin}`);
}
// Make sure each origin has minimum number of connections available for max throughput.
// But only if it's not over H2 which maximizes throughput already.
const minConnections = connections[0].isH2() ? 1 : CONNECTIONS_PER_ORIGIN;
while (connections.length < minConnections) {
connections.push(connections[0].clone());
}
this.connectionsByOrigin.set(origin, connections);
}
}
findAvailableConnectionWithLargestCongestionWindow(connections: TCPConnection[]): TCPConnection|null {
let maxConnection: TCPConnection|null = null;
for (let i = 0; i < connections.length; i++) {
const connection = connections[i];
// Connections that are in use are never available.
if (this._connectionsInUse.has(connection)) {
continue;
}
// This connection is a match and is available! Update our max if it has a larger congestionWindow
const currentMax = (maxConnection?.congestionWindow) || -Infinity;
if (connection.congestionWindow > currentMax) {
maxConnection = connection;
}
}
return maxConnection;
}
/**
* This method finds an available connection to the origin specified by the network request or null
* if no connection was available. If returned, connection will not be available for other network
* records until release is called.
*/
acquire(request: Lantern.NetworkRequest): TCPConnection|null {
if (this.connectionsByRequest.has(request)) {
throw new Core.LanternError('Record already has a connection');
}
const origin = request.parsedURL.securityOrigin;
const connections = this.connectionsByOrigin.get(origin) || [];
const connectionToUse = this.findAvailableConnectionWithLargestCongestionWindow(connections);
if (!connectionToUse) {
return null;
}
this._connectionsInUse.add(connectionToUse);
this.connectionsByRequest.set(request, connectionToUse);
return connectionToUse;
}
/**
* Return the connection currently being used to fetch a request. If no connection
* currently being used for this request, an error will be thrown.
*/
acquireActiveConnectionFromRequest(request: Lantern.NetworkRequest): TCPConnection {
const activeConnection = this.connectionsByRequest.get(request);
if (!activeConnection) {
throw new Core.LanternError('Could not find an active connection for request');
}
return activeConnection;
}
release(request: Lantern.NetworkRequest): void {
const connection = this.connectionsByRequest.get(request);
this.connectionsByRequest.delete(request);
if (connection) {
this._connectionsInUse.delete(connection);
}
}
}