| // Copyright 2024 The Chromium Authors |
| // 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 {TCPConnection} = Lantern.Simulation; |
| |
| describe('TCPConnection', () => { |
| describe('#constructor', () => { |
| it('should create the connection', () => { |
| const rtt = 150; |
| const throughput = 1600 * 1024; |
| const connection = new TCPConnection(rtt, throughput); |
| assert.isOk(connection); |
| assert.strictEqual(connection.rtt, rtt); |
| }); |
| }); |
| |
| describe('#maximumSaturatedConnections', () => { |
| it('should compute number of supported simulated requests', () => { |
| const availableThroughput = 1460 * 8 * 10; // 10 TCP segments/second |
| assert.strictEqual(TCPConnection.maximumSaturatedConnections(100, availableThroughput), 1); |
| assert.strictEqual(TCPConnection.maximumSaturatedConnections(300, availableThroughput), 3); |
| assert.strictEqual(TCPConnection.maximumSaturatedConnections(1000, availableThroughput), 10); |
| }); |
| }); |
| |
| describe('.setWarmed', () => { |
| it('adjusts the time to download appropriately', () => { |
| const connection = new TCPConnection(100, Infinity); |
| assert.strictEqual(connection.simulateDownloadUntil(0).timeElapsed, 300); |
| connection.setWarmed(true); |
| assert.strictEqual(connection.simulateDownloadUntil(0).timeElapsed, 100); |
| }); |
| }); |
| |
| describe('.setCongestionWindow', () => { |
| it('adjusts the time to download appropriately', () => { |
| const connection = new TCPConnection(100, Infinity); |
| assert.deepEqual(connection.simulateDownloadUntil(50000), { |
| bytesDownloaded: 50000, |
| extraBytesDownloaded: 0, |
| congestionWindow: 40, |
| roundTrips: 5, |
| timeElapsed: 500, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| connection.setCongestionWindow(40); // will download all in one round trip |
| assert.deepEqual(connection.simulateDownloadUntil(50000), { |
| bytesDownloaded: 50000, |
| extraBytesDownloaded: 0, |
| congestionWindow: 40, |
| roundTrips: 3, |
| timeElapsed: 300, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| }); |
| }); |
| |
| describe('.setH2OverflowBytesDownloaded', () => { |
| it('adjusts the time to download appropriately for H2 connections', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true, true); |
| connection.setWarmed(true); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 200); |
| connection.setH2OverflowBytesDownloaded(20000); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 100); |
| connection.setH2OverflowBytesDownloaded(50000); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 0); |
| }); |
| |
| it('does not adjust the time to download for non-H2 connections', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true, false); |
| connection.setWarmed(true); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 200); |
| connection.setH2OverflowBytesDownloaded(20000); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 200); |
| connection.setH2OverflowBytesDownloaded(50000); |
| assert.strictEqual(connection.simulateDownloadUntil(30000).timeElapsed, 200); |
| }); |
| }); |
| |
| describe('.simulateDownloadUntil', () => { |
| describe('when maximumTime is not set', () => { |
| it('should provide the correct values small payload non-SSL', () => { |
| const connection = new TCPConnection(100, Infinity, 0, false); |
| assert.deepEqual(connection.simulateDownloadUntil(7300), { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 2, |
| timeElapsed: 200, |
| connectionTiming: { |
| connectionTime: 150, |
| dnsResolutionTime: 0, |
| sslTime: undefined, // non-SSL |
| timeToFirstByte: 200, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values small payload SSL', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true); |
| assert.deepEqual(connection.simulateDownloadUntil(7300), { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 3, |
| timeElapsed: 300, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values small payload H2', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true, true); |
| assert.deepEqual(connection.simulateDownloadUntil(7300), { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 7300, |
| congestionWindow: 10, |
| roundTrips: 3, |
| timeElapsed: 300, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values response time', () => { |
| const responseTime = 78; |
| const connection = new TCPConnection(100, Infinity, responseTime, true); |
| assert.deepEqual(connection.simulateDownloadUntil(7300), { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 3, |
| timeElapsed: 300 + responseTime, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 378, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values large payload', () => { |
| const connection = new TCPConnection(100, 8 * 1000 * 1000); |
| const bytesToDownload = 10 * 1000 * 1000; // 10 mb |
| assert.deepEqual(connection.simulateDownloadUntil(bytesToDownload), { |
| bytesDownloaded: bytesToDownload, |
| extraBytesDownloaded: 0, |
| congestionWindow: 68, |
| roundTrips: 105, |
| timeElapsed: 10500, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values resumed small payload', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true); |
| assert.deepEqual(connection.simulateDownloadUntil(7300, {timeAlreadyElapsed: 250}), { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 3, |
| timeElapsed: 50, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values resumed small payload H2', () => { |
| const connection = new TCPConnection(100, Infinity, 0, true, true); |
| connection.setWarmed(true); |
| connection.setH2OverflowBytesDownloaded(10000); |
| assert.deepEqual(connection.simulateDownloadUntil(7300), { |
| bytesDownloaded: 0, |
| extraBytesDownloaded: 2700, // 10000 - 7300 |
| congestionWindow: 10, |
| roundTrips: 0, |
| timeElapsed: 0, |
| connectionTiming: { |
| timeToFirstByte: 0, |
| }, |
| }); |
| }); |
| |
| it('should provide the correct values resumed large payload', () => { |
| const connection = new TCPConnection(100, 8 * 1000 * 1000); |
| const bytesToDownload = 5 * 1000 * 1000; // 5 mb |
| connection.setCongestionWindow(68); |
| assert.deepEqual( |
| connection.simulateDownloadUntil(bytesToDownload, {timeAlreadyElapsed: 5234}), |
| { |
| bytesDownloaded: bytesToDownload, |
| extraBytesDownloaded: 0, |
| congestionWindow: 68, |
| roundTrips: 51, // 5 mb / (1460 * 68) |
| timeElapsed: 5100, |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }, |
| ); |
| }); |
| }); |
| |
| describe('when maximumTime is set', () => { |
| it('should provide the correct values less than TTFB', () => { |
| const connection = new TCPConnection(100, Infinity, 0, false); |
| assert.deepEqual( |
| connection.simulateDownloadUntil(7300, {timeAlreadyElapsed: 0, maximumTimeToElapse: 68}), |
| { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 2, |
| timeElapsed: 200, |
| connectionTiming: { |
| connectionTime: 150, |
| dnsResolutionTime: 0, |
| sslTime: undefined, // non-SSL |
| timeToFirstByte: 200, |
| }, |
| }, |
| ); |
| }); |
| |
| it('should provide the correct values just over TTFB', () => { |
| const connection = new TCPConnection(100, Infinity, 0, false); |
| assert.deepEqual( |
| connection.simulateDownloadUntil(7300, {timeAlreadyElapsed: 0, maximumTimeToElapse: 250}), |
| { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 2, |
| timeElapsed: 200, |
| connectionTiming: { |
| connectionTime: 150, |
| dnsResolutionTime: 0, |
| sslTime: undefined, // non-SSL |
| timeToFirstByte: 200, |
| }, |
| }, |
| ); |
| }); |
| |
| it('should provide the correct values with already elapsed', () => { |
| const connection = new TCPConnection(100, Infinity, 0, false); |
| assert.deepEqual( |
| connection.simulateDownloadUntil(7300, { |
| timeAlreadyElapsed: 75, |
| maximumTimeToElapse: 250, |
| }), |
| { |
| bytesDownloaded: 7300, |
| extraBytesDownloaded: 0, |
| congestionWindow: 10, |
| roundTrips: 2, |
| timeElapsed: 125, |
| connectionTiming: { |
| connectionTime: 150, |
| dnsResolutionTime: 0, |
| sslTime: undefined, // non-SSL |
| timeToFirstByte: 200, |
| }, |
| }, |
| ); |
| }); |
| |
| it('should provide the correct values large payloads', () => { |
| const connection = new TCPConnection(100, 8 * 1000 * 1000); |
| const bytesToDownload = 10 * 1000 * 1000; // 10 mb |
| assert.deepEqual( |
| connection.simulateDownloadUntil(bytesToDownload, { |
| timeAlreadyElapsed: 500, |
| maximumTimeToElapse: 740, |
| }), |
| { |
| bytesDownloaded: 683280, // should be less than 68 * 1460 * 8 |
| extraBytesDownloaded: 0, |
| congestionWindow: 68, |
| roundTrips: 8, |
| timeElapsed: 800, // skips the handshake because time already elapsed |
| connectionTiming: { |
| connectionTime: 250, |
| dnsResolutionTime: 0, |
| sslTime: 100, |
| timeToFirstByte: 300, |
| }, |
| }, |
| ); |
| }); |
| |
| it('should all add up', () => { |
| const connection = new TCPConnection(100, 8 * 1000 * 1000); |
| const bytesToDownload = 10 * 1000 * 1000; // 10 mb |
| const firstStoppingPoint = 5234; |
| const secondStoppingPoint = 315; |
| const thirdStoppingPoint = 10500 - firstStoppingPoint - secondStoppingPoint; |
| |
| const firstSegment = connection.simulateDownloadUntil(bytesToDownload, { |
| timeAlreadyElapsed: 0, |
| maximumTimeToElapse: firstStoppingPoint, |
| }); |
| const firstOvershoot = firstSegment.timeElapsed - firstStoppingPoint; |
| |
| connection.setCongestionWindow(firstSegment.congestionWindow); |
| const secondSegment = connection.simulateDownloadUntil( |
| bytesToDownload - firstSegment.bytesDownloaded, |
| { |
| timeAlreadyElapsed: firstSegment.timeElapsed, |
| maximumTimeToElapse: secondStoppingPoint - firstOvershoot, |
| }, |
| ); |
| const secondOvershoot = firstOvershoot + secondSegment.timeElapsed - secondStoppingPoint; |
| |
| connection.setCongestionWindow(secondSegment.congestionWindow); |
| const thirdSegment = connection.simulateDownloadUntil( |
| bytesToDownload - firstSegment.bytesDownloaded - secondSegment.bytesDownloaded, |
| {timeAlreadyElapsed: firstSegment.timeElapsed + secondSegment.timeElapsed}, |
| ); |
| const thirdOvershoot = secondOvershoot + thirdSegment.timeElapsed - thirdStoppingPoint; |
| |
| assert.strictEqual(thirdOvershoot, 0); |
| assert.strictEqual( |
| firstSegment.bytesDownloaded + secondSegment.bytesDownloaded + thirdSegment.bytesDownloaded, |
| bytesToDownload, |
| ); |
| assert.strictEqual( |
| firstSegment.timeElapsed + secondSegment.timeElapsed + thirdSegment.timeElapsed, |
| 10500, |
| ); |
| }); |
| }); |
| }); |
| }); |