| // Copyright 2016 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. |
| |
| #include "quic/core/congestion_control/bbr_sender.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "quic/core/congestion_control/rtt_stats.h" |
| #include "quic/core/quic_bandwidth.h" |
| #include "quic/core/quic_packets.h" |
| #include "quic/core/quic_types.h" |
| #include "quic/core/quic_utils.h" |
| #include "quic/platform/api/quic_flags.h" |
| #include "quic/platform/api/quic_logging.h" |
| #include "quic/platform/api/quic_test.h" |
| #include "quic/test_tools/mock_clock.h" |
| #include "quic/test_tools/quic_config_peer.h" |
| #include "quic/test_tools/quic_connection_peer.h" |
| #include "quic/test_tools/quic_sent_packet_manager_peer.h" |
| #include "quic/test_tools/quic_test_utils.h" |
| #include "quic/test_tools/send_algorithm_test_result.pb.h" |
| #include "quic/test_tools/send_algorithm_test_utils.h" |
| #include "quic/test_tools/simulator/quic_endpoint.h" |
| #include "quic/test_tools/simulator/simulator.h" |
| #include "quic/test_tools/simulator/switch.h" |
| |
| using testing::AllOf; |
| using testing::Ge; |
| using testing::Le; |
| |
| DEFINE_QUIC_COMMAND_LINE_FLAG( |
| std::string, |
| quic_bbr_test_regression_mode, |
| "", |
| "One of a) 'record' to record test result (one file per test), or " |
| "b) 'regress' to regress against recorded results, or " |
| "c) <anything else> for non-regression mode."); |
| |
| namespace quic { |
| namespace test { |
| |
| // Use the initial CWND of 10, as 32 is too much for the test network. |
| const uint32_t kInitialCongestionWindowPackets = 10; |
| const uint32_t kDefaultWindowTCP = |
| kInitialCongestionWindowPackets * kDefaultTCPMSS; |
| |
| // Test network parameters. Here, the topology of the network is: |
| // |
| // BBR sender |
| // | |
| // | <-- local link (10 Mbps, 2 ms delay) |
| // | |
| // Network switch |
| // * <-- the bottleneck queue in the direction |
| // | of the receiver |
| // | |
| // | <-- test link (4 Mbps, 30 ms delay) |
| // | |
| // | |
| // Receiver |
| // |
| // The reason the bandwidths chosen are relatively low is the fact that the |
| // connection simulator uses QuicTime for its internal clock, and as such has |
| // the granularity of 1us, meaning that at bandwidth higher than 20 Mbps the |
| // packets can start to land on the same timestamp. |
| const QuicBandwidth kTestLinkBandwidth = |
| QuicBandwidth::FromKBitsPerSecond(4000); |
| const QuicBandwidth kLocalLinkBandwidth = |
| QuicBandwidth::FromKBitsPerSecond(10000); |
| const QuicTime::Delta kTestPropagationDelay = |
| QuicTime::Delta::FromMilliseconds(30); |
| const QuicTime::Delta kLocalPropagationDelay = |
| QuicTime::Delta::FromMilliseconds(2); |
| const QuicTime::Delta kTestTransferTime = |
| kTestLinkBandwidth.TransferTime(kMaxOutgoingPacketSize) + |
| kLocalLinkBandwidth.TransferTime(kMaxOutgoingPacketSize); |
| const QuicTime::Delta kTestRtt = |
| (kTestPropagationDelay + kLocalPropagationDelay + kTestTransferTime) * 2; |
| const QuicByteCount kTestBdp = kTestRtt * kTestLinkBandwidth; |
| |
| class BbrSenderTest : public QuicTest { |
| protected: |
| BbrSenderTest() |
| : simulator_(&random_), |
| bbr_sender_(&simulator_, |
| "BBR sender", |
| "Receiver", |
| Perspective::IS_CLIENT, |
| /*connection_id=*/TestConnectionId(42)), |
| competing_sender_(&simulator_, |
| "Competing sender", |
| "Competing receiver", |
| Perspective::IS_CLIENT, |
| /*connection_id=*/TestConnectionId(43)), |
| receiver_(&simulator_, |
| "Receiver", |
| "BBR sender", |
| Perspective::IS_SERVER, |
| /*connection_id=*/TestConnectionId(42)), |
| competing_receiver_(&simulator_, |
| "Competing receiver", |
| "Competing sender", |
| Perspective::IS_SERVER, |
| /*connection_id=*/TestConnectionId(43)), |
| receiver_multiplexer_("Receiver multiplexer", |
| {&receiver_, &competing_receiver_}) { |
| rtt_stats_ = bbr_sender_.connection()->sent_packet_manager().GetRttStats(); |
| sender_ = SetupBbrSender(&bbr_sender_); |
| |
| clock_ = simulator_.GetClock(); |
| } |
| |
| void SetUp() override { |
| if (GetQuicFlag(FLAGS_quic_bbr_test_regression_mode) == "regress") { |
| SendAlgorithmTestResult expected; |
| ASSERT_TRUE(LoadSendAlgorithmTestResult(&expected)); |
| random_seed_ = expected.random_seed(); |
| } else { |
| random_seed_ = QuicRandom::GetInstance()->RandUint64(); |
| } |
| random_.set_seed(random_seed_); |
| QUIC_LOG(INFO) << "BbrSenderTest simulator set up. Seed: " << random_seed_; |
| } |
| |
| ~BbrSenderTest() { |
| const std::string regression_mode = |
| GetQuicFlag(FLAGS_quic_bbr_test_regression_mode); |
| const QuicTime::Delta simulated_duration = clock_->Now() - QuicTime::Zero(); |
| if (regression_mode == "record") { |
| RecordSendAlgorithmTestResult(random_seed_, |
| simulated_duration.ToMicroseconds()); |
| } else if (regression_mode == "regress") { |
| CompareSendAlgorithmTestResult(simulated_duration.ToMicroseconds()); |
| } |
| } |
| |
| uint64_t random_seed_; |
| SimpleRandom random_; |
| simulator::Simulator simulator_; |
| simulator::QuicEndpoint bbr_sender_; |
| simulator::QuicEndpoint competing_sender_; |
| simulator::QuicEndpoint receiver_; |
| simulator::QuicEndpoint competing_receiver_; |
| simulator::QuicEndpointMultiplexer receiver_multiplexer_; |
| std::unique_ptr<simulator::Switch> switch_; |
| std::unique_ptr<simulator::SymmetricLink> bbr_sender_link_; |
| std::unique_ptr<simulator::SymmetricLink> competing_sender_link_; |
| std::unique_ptr<simulator::SymmetricLink> receiver_link_; |
| |
| // Owned by different components of the connection. |
| const QuicClock* clock_; |
| const RttStats* rtt_stats_; |
| BbrSender* sender_; |
| |
| // Enables BBR on |endpoint| and returns the associated BBR congestion |
| // controller. |
| BbrSender* SetupBbrSender(simulator::QuicEndpoint* endpoint) { |
| const RttStats* rtt_stats = |
| endpoint->connection()->sent_packet_manager().GetRttStats(); |
| // Ownership of the sender will be overtaken by the endpoint. |
| BbrSender* sender = new BbrSender( |
| endpoint->connection()->clock()->Now(), rtt_stats, |
| QuicSentPacketManagerPeer::GetUnackedPacketMap( |
| QuicConnectionPeer::GetSentPacketManager(endpoint->connection())), |
| kInitialCongestionWindowPackets, |
| GetQuicFlag(FLAGS_quic_max_congestion_window), &random_, |
| QuicConnectionPeer::GetStats(endpoint->connection())); |
| QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender); |
| endpoint->RecordTrace(); |
| return sender; |
| } |
| |
| // Creates a default setup, which is a network with a bottleneck between the |
| // receiver and the switch. The switch has the buffers four times larger than |
| // the bottleneck BDP, which should guarantee a lack of losses. |
| void CreateDefaultSetup() { |
| switch_ = std::make_unique<simulator::Switch>(&simulator_, "Switch", 8, |
| 2 * kTestBdp); |
| bbr_sender_link_ = std::make_unique<simulator::SymmetricLink>( |
| &bbr_sender_, switch_->port(1), kLocalLinkBandwidth, |
| kLocalPropagationDelay); |
| receiver_link_ = std::make_unique<simulator::SymmetricLink>( |
| &receiver_, switch_->port(2), kTestLinkBandwidth, |
| kTestPropagationDelay); |
| } |
| |
| // Same as the default setup, except the buffer now is half of the BDP. |
| void CreateSmallBufferSetup() { |
| switch_ = std::make_unique<simulator::Switch>(&simulator_, "Switch", 8, |
| 0.5 * kTestBdp); |
| bbr_sender_link_ = std::make_unique<simulator::SymmetricLink>( |
| &bbr_sender_, switch_->port(1), kLocalLinkBandwidth, |
| kLocalPropagationDelay); |
| receiver_link_ = std::make_unique<simulator::SymmetricLink>( |
| &receiver_, switch_->port(2), kTestLinkBandwidth, |
| kTestPropagationDelay); |
| } |
| |
| // Creates the variation of the default setup in which there is another sender |
| // that competes for the same bottleneck link. |
| void CreateCompetitionSetup() { |
| switch_ = std::make_unique<simulator::Switch>(&simulator_, "Switch", 8, |
| 2 * kTestBdp); |
| |
| // Add a small offset to the competing link in order to avoid |
| // synchronization effects. |
| const QuicTime::Delta small_offset = QuicTime::Delta::FromMicroseconds(3); |
| bbr_sender_link_ = std::make_unique<simulator::SymmetricLink>( |
| &bbr_sender_, switch_->port(1), kLocalLinkBandwidth, |
| kLocalPropagationDelay); |
| competing_sender_link_ = std::make_unique<simulator::SymmetricLink>( |
| &competing_sender_, switch_->port(3), kLocalLinkBandwidth, |
| kLocalPropagationDelay + small_offset); |
| receiver_link_ = std::make_unique<simulator::SymmetricLink>( |
| &receiver_multiplexer_, switch_->port(2), kTestLinkBandwidth, |
| kTestPropagationDelay); |
| } |
| |
| // Creates a BBR vs BBR competition setup. |
| void CreateBbrVsBbrSetup() { |
| SetupBbrSender(&competing_sender_); |
| CreateCompetitionSetup(); |
| } |
| |
| void EnableAggregation(QuicByteCount aggregation_bytes, |
| QuicTime::Delta aggregation_timeout) { |
| // Enable aggregation on the path from the receiver to the sender. |
| switch_->port_queue(1)->EnableAggregation(aggregation_bytes, |
| aggregation_timeout); |
| } |
| |
| void DoSimpleTransfer(QuicByteCount transfer_size, QuicTime::Delta deadline) { |
| bbr_sender_.AddBytesToTransfer(transfer_size); |
| // TODO(vasilvv): consider rewriting this to run until the receiver actually |
| // receives the intended amount of bytes. |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return bbr_sender_.bytes_to_transfer() == 0; }, deadline); |
| EXPECT_TRUE(simulator_result) |
| << "Simple transfer failed. Bytes remaining: " |
| << bbr_sender_.bytes_to_transfer(); |
| QUIC_LOG(INFO) << "Simple transfer state: " << sender_->ExportDebugState(); |
| } |
| |
| // Drive the simulator by sending enough data to enter PROBE_BW. |
| void DriveOutOfStartup() { |
| ASSERT_FALSE(sender_->ExportDebugState().is_at_full_bandwidth); |
| DoSimpleTransfer(1024 * 1024, QuicTime::Delta::FromSeconds(15)); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.02f); |
| } |
| |
| // Send |bytes|-sized bursts of data |number_of_bursts| times, waiting for |
| // |wait_time| between each burst. |
| void SendBursts(size_t number_of_bursts, |
| QuicByteCount bytes, |
| QuicTime::Delta wait_time) { |
| ASSERT_EQ(0u, bbr_sender_.bytes_to_transfer()); |
| for (size_t i = 0; i < number_of_bursts; i++) { |
| bbr_sender_.AddBytesToTransfer(bytes); |
| |
| // Transfer data and wait for three seconds between each transfer. |
| simulator_.RunFor(wait_time); |
| |
| // Ensure the connection did not time out. |
| ASSERT_TRUE(bbr_sender_.connection()->connected()); |
| ASSERT_TRUE(receiver_.connection()->connected()); |
| } |
| |
| simulator_.RunFor(wait_time + kTestRtt); |
| ASSERT_EQ(0u, bbr_sender_.bytes_to_transfer()); |
| } |
| |
| void SetConnectionOption(QuicTag option) { |
| QuicConfig config; |
| QuicTagVector options; |
| options.push_back(option); |
| QuicConfigPeer::SetReceivedConnectionOptions(&config, options); |
| sender_->SetFromConfig(config, Perspective::IS_SERVER); |
| } |
| }; |
| |
| TEST_F(BbrSenderTest, SetInitialCongestionWindow) { |
| EXPECT_NE(3u * kDefaultTCPMSS, sender_->GetCongestionWindow()); |
| sender_->SetInitialCongestionWindowInPackets(3); |
| EXPECT_EQ(3u * kDefaultTCPMSS, sender_->GetCongestionWindow()); |
| } |
| |
| // Test a simple long data transfer in the default setup. |
| TEST_F(BbrSenderTest, SimpleTransfer) { |
| CreateDefaultSetup(); |
| |
| // At startup make sure we are at the default. |
| EXPECT_EQ(kDefaultWindowTCP, sender_->GetCongestionWindow()); |
| // At startup make sure we can send. |
| EXPECT_TRUE(sender_->CanSend(0)); |
| // And that window is un-affected. |
| EXPECT_EQ(kDefaultWindowTCP, sender_->GetCongestionWindow()); |
| |
| // Verify that Sender is in slow start. |
| EXPECT_TRUE(sender_->InSlowStart()); |
| |
| // Verify that pacing rate is based on the initial RTT. |
| QuicBandwidth expected_pacing_rate = QuicBandwidth::FromBytesAndTimeDelta( |
| 2.885 * kDefaultWindowTCP, rtt_stats_->initial_rtt()); |
| EXPECT_APPROX_EQ(expected_pacing_rate.ToBitsPerSecond(), |
| sender_->PacingRate(0).ToBitsPerSecond(), 0.01f); |
| |
| ASSERT_GE(kTestBdp, kDefaultWindowTCP + kDefaultTCPMSS); |
| |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| |
| // The margin here is quite high, since there exists a possibility that the |
| // connection just exited high gain cycle. |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->smoothed_rtt(), 0.2f); |
| } |
| |
| // Test a simple transfer in a situation when the buffer is less than BDP. |
| TEST_F(BbrSenderTest, SimpleTransferSmallBuffer) { |
| CreateSmallBufferSetup(); |
| |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| EXPECT_GE(bbr_sender_.connection()->GetStats().packets_lost, 0u); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| |
| // The margin here is quite high, since there exists a possibility that the |
| // connection just exited high gain cycle. |
| EXPECT_APPROX_EQ(kTestRtt, sender_->GetMinRtt(), 0.2f); |
| } |
| |
| TEST_F(BbrSenderTest, RemoveBytesLostInRecovery) { |
| CreateDefaultSetup(); |
| |
| DriveOutOfStartup(); |
| |
| // Drop a packet to enter recovery. |
| receiver_.DropNextIncomingPacket(); |
| ASSERT_TRUE( |
| simulator_.RunUntilOrTimeout([this]() { return sender_->InRecovery(); }, |
| QuicTime::Delta::FromSeconds(30))); |
| |
| QuicUnackedPacketMap* unacked_packets = |
| QuicSentPacketManagerPeer::GetUnackedPacketMap( |
| QuicConnectionPeer::GetSentPacketManager(bbr_sender_.connection())); |
| QuicPacketNumber largest_sent = |
| bbr_sender_.connection()->sent_packet_manager().GetLargestSentPacket(); |
| // least_inflight is the smallest inflight packet. |
| QuicPacketNumber least_inflight = |
| bbr_sender_.connection()->sent_packet_manager().GetLeastUnacked(); |
| while (!unacked_packets->GetTransmissionInfo(least_inflight).in_flight) { |
| ASSERT_LE(least_inflight, largest_sent); |
| least_inflight++; |
| } |
| QuicPacketLength least_inflight_packet_size = |
| unacked_packets->GetTransmissionInfo(least_inflight).bytes_sent; |
| QuicByteCount prior_recovery_window = |
| sender_->ExportDebugState().recovery_window; |
| QuicByteCount prior_inflight = unacked_packets->bytes_in_flight(); |
| QUIC_LOG(INFO) << "Recovery window:" << prior_recovery_window |
| << ", least_inflight_packet_size:" |
| << least_inflight_packet_size |
| << ", bytes_in_flight:" << prior_inflight; |
| ASSERT_GT(prior_recovery_window, least_inflight_packet_size); |
| |
| // Lose the least inflight packet and expect the recovery window to drop. |
| unacked_packets->RemoveFromInFlight(least_inflight); |
| LostPacketVector lost_packets; |
| lost_packets.emplace_back(least_inflight, least_inflight_packet_size); |
| sender_->OnCongestionEvent(false, prior_inflight, clock_->Now(), {}, |
| lost_packets); |
| EXPECT_EQ(sender_->ExportDebugState().recovery_window, |
| prior_inflight - least_inflight_packet_size); |
| EXPECT_LT(sender_->ExportDebugState().recovery_window, prior_recovery_window); |
| } |
| |
| // Test a simple long data transfer with 2 rtts of aggregation. |
| TEST_F(BbrSenderTest, SimpleTransfer2RTTAggregationBytes) { |
| SetConnectionOption(kBSAO); |
| CreateDefaultSetup(); |
| // 2 RTTs of aggregation, with a max of 10kb. |
| EnableAggregation(10 * 1024, 2 * kTestRtt); |
| |
| // Transfer 12MB. |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); |
| EXPECT_TRUE(sender_->ExportDebugState().mode == BbrSender::PROBE_BW || |
| sender_->ExportDebugState().mode == BbrSender::PROBE_RTT); |
| |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| |
| // The margin here is high, because the aggregation greatly increases |
| // smoothed rtt. |
| EXPECT_GE(kTestRtt * 4, rtt_stats_->smoothed_rtt()); |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->min_rtt(), 0.5f); |
| } |
| |
| // Test a simple long data transfer with 2 rtts of aggregation. |
| TEST_F(BbrSenderTest, SimpleTransferAckDecimation) { |
| SetConnectionOption(kBSAO); |
| // Decrease the CWND gain so extra CWND is required with stretch acks. |
| SetQuicFlag(FLAGS_quic_bbr_cwnd_gain, 1.0); |
| sender_ = new BbrSender( |
| bbr_sender_.connection()->clock()->Now(), rtt_stats_, |
| QuicSentPacketManagerPeer::GetUnackedPacketMap( |
| QuicConnectionPeer::GetSentPacketManager(bbr_sender_.connection())), |
| kInitialCongestionWindowPackets, |
| GetQuicFlag(FLAGS_quic_max_congestion_window), &random_, |
| QuicConnectionPeer::GetStats(bbr_sender_.connection())); |
| QuicConnectionPeer::SetSendAlgorithm(bbr_sender_.connection(), sender_); |
| CreateDefaultSetup(); |
| |
| // Transfer 12MB. |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| |
| // TODO(ianswett): Expect 0 packets are lost once BBR no longer measures |
| // bandwidth higher than the link rate. |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| // The margin here is high, because the aggregation greatly increases |
| // smoothed rtt. |
| EXPECT_GE(kTestRtt * 2, rtt_stats_->smoothed_rtt()); |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->min_rtt(), 0.1f); |
| } |
| |
| // Test a simple long data transfer with 2 rtts of aggregation. |
| // TODO(b/172302465) Re-enable this test. |
| TEST_F(BbrSenderTest, |
| QUIC_TEST_DISABLED_IN_CHROME( |
| SimpleTransfer2RTTAggregationBytes20RTTWindow)) { |
| SetConnectionOption(kBSAO); |
| CreateDefaultSetup(); |
| SetConnectionOption(kBBR4); |
| // 2 RTTs of aggregation, with a max of 10kb. |
| EnableAggregation(10 * 1024, 2 * kTestRtt); |
| |
| // Transfer 12MB. |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); |
| EXPECT_TRUE(sender_->ExportDebugState().mode == BbrSender::PROBE_BW || |
| sender_->ExportDebugState().mode == BbrSender::PROBE_RTT); |
| |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| |
| // TODO(ianswett): Expect 0 packets are lost once BBR no longer measures |
| // bandwidth higher than the link rate. |
| // The margin here is high, because the aggregation greatly increases |
| // smoothed rtt. |
| EXPECT_GE(kTestRtt * 4, rtt_stats_->smoothed_rtt()); |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->min_rtt(), 0.25f); |
| } |
| |
| // Test a simple long data transfer with 2 rtts of aggregation. |
| TEST_F(BbrSenderTest, SimpleTransfer2RTTAggregationBytes40RTTWindow) { |
| SetConnectionOption(kBSAO); |
| CreateDefaultSetup(); |
| SetConnectionOption(kBBR5); |
| // 2 RTTs of aggregation, with a max of 10kb. |
| EnableAggregation(10 * 1024, 2 * kTestRtt); |
| |
| // Transfer 12MB. |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35)); |
| EXPECT_TRUE(sender_->ExportDebugState().mode == BbrSender::PROBE_BW || |
| sender_->ExportDebugState().mode == BbrSender::PROBE_RTT); |
| |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| |
| // TODO(ianswett): Expect 0 packets are lost once BBR no longer measures |
| // bandwidth higher than the link rate. |
| // The margin here is high, because the aggregation greatly increases |
| // smoothed rtt. |
| EXPECT_GE(kTestRtt * 4, rtt_stats_->smoothed_rtt()); |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->min_rtt(), 0.25f); |
| } |
| |
| // Test the number of losses incurred by the startup phase in a situation when |
| // the buffer is less than BDP. |
| TEST_F(BbrSenderTest, PacketLossOnSmallBufferStartup) { |
| CreateSmallBufferSetup(); |
| |
| DriveOutOfStartup(); |
| float loss_rate = |
| static_cast<float>(bbr_sender_.connection()->GetStats().packets_lost) / |
| bbr_sender_.connection()->GetStats().packets_sent; |
| EXPECT_LE(loss_rate, 0.31); |
| } |
| |
| // Test the number of losses incurred by the startup phase in a situation when |
| // the buffer is less than BDP, with a STARTUP CWND gain of 2. |
| TEST_F(BbrSenderTest, PacketLossOnSmallBufferStartupDerivedCWNDGain) { |
| CreateSmallBufferSetup(); |
| |
| SetConnectionOption(kBBQ2); |
| DriveOutOfStartup(); |
| float loss_rate = |
| static_cast<float>(bbr_sender_.connection()->GetStats().packets_lost) / |
| bbr_sender_.connection()->GetStats().packets_sent; |
| EXPECT_LE(loss_rate, 0.1); |
| } |
| |
| // Ensures the code transitions loss recovery states correctly (NOT_IN_RECOVERY |
| // -> CONSERVATION -> GROWTH -> NOT_IN_RECOVERY). |
| TEST_F(BbrSenderTest, RecoveryStates) { |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); |
| bool simulator_result; |
| CreateSmallBufferSetup(); |
| |
| bbr_sender_.AddBytesToTransfer(100 * 1024 * 1024); |
| ASSERT_EQ(BbrSender::NOT_IN_RECOVERY, |
| sender_->ExportDebugState().recovery_state); |
| |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().recovery_state != |
| BbrSender::NOT_IN_RECOVERY; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::CONSERVATION, |
| sender_->ExportDebugState().recovery_state); |
| |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().recovery_state != |
| BbrSender::CONSERVATION; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::GROWTH, sender_->ExportDebugState().recovery_state); |
| |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().recovery_state != BbrSender::GROWTH; |
| }, |
| timeout); |
| |
| ASSERT_EQ(BbrSender::NOT_IN_RECOVERY, |
| sender_->ExportDebugState().recovery_state); |
| ASSERT_TRUE(simulator_result); |
| } |
| |
| // Verify the behavior of the algorithm in the case when the connection sends |
| // small bursts of data after sending continuously for a while. |
| TEST_F(BbrSenderTest, ApplicationLimitedBursts) { |
| CreateDefaultSetup(); |
| |
| DriveOutOfStartup(); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| |
| SendBursts(20, 512, QuicTime::Delta::FromSeconds(3)); |
| EXPECT_TRUE(sender_->ExportDebugState().last_sample_is_app_limited); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| } |
| |
| // Verify the behavior of the algorithm in the case when the connection sends |
| // small bursts of data and then starts sending continuously. |
| TEST_F(BbrSenderTest, ApplicationLimitedBurstsWithoutPrior) { |
| CreateDefaultSetup(); |
| |
| SendBursts(40, 512, QuicTime::Delta::FromSeconds(3)); |
| EXPECT_TRUE(sender_->ExportDebugState().last_sample_is_app_limited); |
| |
| DriveOutOfStartup(); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| // Verify that the DRAIN phase works correctly. |
| TEST_F(BbrSenderTest, Drain) { |
| CreateDefaultSetup(); |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); |
| // Get the queue at the bottleneck, which is the outgoing queue at the port to |
| // which the receiver is connected. |
| const simulator::Queue* queue = switch_->port_queue(2); |
| bool simulator_result; |
| |
| // We have no intention of ever finishing this transfer. |
| bbr_sender_.AddBytesToTransfer(100 * 1024 * 1024); |
| |
| // Run the startup, and verify that it fills up the queue. |
| ASSERT_EQ(BbrSender::STARTUP, sender_->ExportDebugState().mode); |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().mode != BbrSender::STARTUP; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_APPROX_EQ(sender_->BandwidthEstimate() * (1 / 2.885f), |
| sender_->PacingRate(0), 0.01f); |
| |
| // BBR uses CWND gain of 2 during STARTUP, hence it will fill the buffer |
| // with approximately 1 BDP. Here, we use 0.8 to give some margin for |
| // error. |
| EXPECT_GE(queue->bytes_queued(), 0.8 * kTestBdp); |
| |
| // Observe increased RTT due to bufferbloat. |
| const QuicTime::Delta queueing_delay = |
| kTestLinkBandwidth.TransferTime(queue->bytes_queued()); |
| EXPECT_APPROX_EQ(kTestRtt + queueing_delay, rtt_stats_->latest_rtt(), 0.1f); |
| |
| // Transition to the drain phase and verify that it makes the queue |
| // have at most a BDP worth of packets. |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().mode != BbrSender::DRAIN; }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_LE(queue->bytes_queued(), kTestBdp); |
| |
| // Wait for a few round trips and ensure we're in appropriate phase of gain |
| // cycling before taking an RTT measurement. |
| const QuicRoundTripCount start_round_trip = |
| sender_->ExportDebugState().round_trip_count; |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this, start_round_trip]() { |
| QuicRoundTripCount rounds_passed = |
| sender_->ExportDebugState().round_trip_count - start_round_trip; |
| return rounds_passed >= 4 && |
| sender_->ExportDebugState().gain_cycle_index == 7; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Observe the bufferbloat go away. |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->smoothed_rtt(), 0.1f); |
| } |
| |
| // TODO(wub): Re-enable this test once default drain_gain changed to 0.75. |
| // Verify that the DRAIN phase works correctly. |
| TEST_F(BbrSenderTest, DISABLED_ShallowDrain) { |
| CreateDefaultSetup(); |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); |
| // Get the queue at the bottleneck, which is the outgoing queue at the port to |
| // which the receiver is connected. |
| const simulator::Queue* queue = switch_->port_queue(2); |
| bool simulator_result; |
| |
| // We have no intention of ever finishing this transfer. |
| bbr_sender_.AddBytesToTransfer(100 * 1024 * 1024); |
| |
| // Run the startup, and verify that it fills up the queue. |
| ASSERT_EQ(BbrSender::STARTUP, sender_->ExportDebugState().mode); |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().mode != BbrSender::STARTUP; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(0.75 * sender_->BandwidthEstimate(), sender_->PacingRate(0)); |
| // BBR uses CWND gain of 2.88 during STARTUP, hence it will fill the buffer |
| // with approximately 1.88 BDPs. Here, we use 1.5 to give some margin for |
| // error. |
| EXPECT_GE(queue->bytes_queued(), 1.5 * kTestBdp); |
| |
| // Observe increased RTT due to bufferbloat. |
| const QuicTime::Delta queueing_delay = |
| kTestLinkBandwidth.TransferTime(queue->bytes_queued()); |
| EXPECT_APPROX_EQ(kTestRtt + queueing_delay, rtt_stats_->latest_rtt(), 0.1f); |
| |
| // Transition to the drain phase and verify that it makes the queue |
| // have at most a BDP worth of packets. |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().mode != BbrSender::DRAIN; }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_LE(queue->bytes_queued(), kTestBdp); |
| |
| // Wait for a few round trips and ensure we're in appropriate phase of gain |
| // cycling before taking an RTT measurement. |
| const QuicRoundTripCount start_round_trip = |
| sender_->ExportDebugState().round_trip_count; |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this, start_round_trip]() { |
| QuicRoundTripCount rounds_passed = |
| sender_->ExportDebugState().round_trip_count - start_round_trip; |
| return rounds_passed >= 4 && |
| sender_->ExportDebugState().gain_cycle_index == 7; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Observe the bufferbloat go away. |
| EXPECT_APPROX_EQ(kTestRtt, rtt_stats_->smoothed_rtt(), 0.1f); |
| } |
| |
| // Verify that the connection enters and exits PROBE_RTT correctly. |
| TEST_F(BbrSenderTest, ProbeRtt) { |
| CreateDefaultSetup(); |
| DriveOutOfStartup(); |
| |
| // We have no intention of ever finishing this transfer. |
| bbr_sender_.AddBytesToTransfer(100 * 1024 * 1024); |
| |
| // Wait until the connection enters PROBE_RTT. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(12); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().mode == BbrSender::PROBE_RTT; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::PROBE_RTT, sender_->ExportDebugState().mode); |
| |
| // Exit PROBE_RTT. |
| const QuicTime probe_rtt_start = clock_->Now(); |
| const QuicTime::Delta time_to_exit_probe_rtt = |
| kTestRtt + QuicTime::Delta::FromMilliseconds(200); |
| simulator_.RunFor(1.5 * time_to_exit_probe_rtt); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_GE(sender_->ExportDebugState().min_rtt_timestamp, probe_rtt_start); |
| } |
| |
| // Ensure that a connection that is app-limited and is at sufficiently low |
| // bandwidth will not exit high gain phase, and similarly ensure that the |
| // connection will exit low gain early if the number of bytes in flight is low. |
| // TODO(crbug.com/1145095): Re-enable this test. |
| TEST_F(BbrSenderTest, QUIC_TEST_DISABLED_IN_CHROME(InFlightAwareGainCycling)) { |
| CreateDefaultSetup(); |
| DriveOutOfStartup(); |
| |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| while (!(sender_->ExportDebugState().gain_cycle_index >= 4 && |
| bbr_sender_.bytes_to_transfer() == 0)) { |
| bbr_sender_.AddBytesToTransfer(kTestLinkBandwidth.ToBytesPerSecond()); |
| ASSERT_TRUE(simulator_.RunUntilOrTimeout( |
| [this]() { return bbr_sender_.bytes_to_transfer() == 0; }, timeout)); |
| } |
| |
| // Send at 10% of available rate. Run for 3 seconds, checking in the middle |
| // and at the end. The pacing gain should be high throughout. |
| QuicBandwidth target_bandwidth = 0.1f * kTestLinkBandwidth; |
| QuicTime::Delta burst_interval = QuicTime::Delta::FromMilliseconds(300); |
| for (int i = 0; i < 2; i++) { |
| SendBursts(5, target_bandwidth * burst_interval, burst_interval); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_EQ(0, sender_->ExportDebugState().gain_cycle_index); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.02f); |
| } |
| |
| // Now that in-flight is almost zero and the pacing gain is still above 1, |
| // send approximately 1.25 BDPs worth of data. This should cause the |
| // PROBE_BW mode to enter low gain cycle, and exit it earlier than one min_rtt |
| // due to running out of data to send. |
| bbr_sender_.AddBytesToTransfer(1.3 * kTestBdp); |
| ASSERT_TRUE(simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().gain_cycle_index == 1; }, |
| timeout)); |
| |
| simulator_.RunFor(0.75 * sender_->ExportDebugState().min_rtt); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_EQ(2, sender_->ExportDebugState().gain_cycle_index); |
| } |
| |
| // Ensure that the pacing rate does not drop at startup. |
| TEST_F(BbrSenderTest, NoBandwidthDropOnStartup) { |
| CreateDefaultSetup(); |
| |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result; |
| |
| QuicBandwidth initial_rate = QuicBandwidth::FromBytesAndTimeDelta( |
| kInitialCongestionWindowPackets * kDefaultTCPMSS, |
| rtt_stats_->initial_rtt()); |
| EXPECT_GE(sender_->PacingRate(0), initial_rate); |
| |
| // Send a packet. |
| bbr_sender_.AddBytesToTransfer(1000); |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return receiver_.bytes_received() == 1000; }, timeout); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_GE(sender_->PacingRate(0), initial_rate); |
| |
| // Wait for a while. |
| simulator_.RunFor(QuicTime::Delta::FromSeconds(2)); |
| EXPECT_GE(sender_->PacingRate(0), initial_rate); |
| |
| // Send another packet. |
| bbr_sender_.AddBytesToTransfer(1000); |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return receiver_.bytes_received() == 2000; }, timeout); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_GE(sender_->PacingRate(0), initial_rate); |
| } |
| |
| // Test exiting STARTUP earlier due to the 1RTT connection option. |
| TEST_F(BbrSenderTest, SimpleTransfer1RTTStartup) { |
| CreateDefaultSetup(); |
| |
| SetConnectionOption(k1RTT); |
| EXPECT_EQ(1u, sender_->num_startup_rtts()); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| QuicRoundTripCount max_bw_round = 0; |
| QuicBandwidth max_bw(QuicBandwidth::Zero()); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this, &max_bw, &max_bw_round]() { |
| if (max_bw < sender_->ExportDebugState().max_bandwidth) { |
| max_bw = sender_->ExportDebugState().max_bandwidth; |
| max_bw_round = sender_->ExportDebugState().round_trip_count; |
| } |
| return sender_->ExportDebugState().is_at_full_bandwidth; |
| }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(1u, sender_->ExportDebugState().round_trip_count - max_bw_round); |
| EXPECT_EQ(1u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| // Test exiting STARTUP earlier due to the 2RTT connection option. |
| TEST_F(BbrSenderTest, SimpleTransfer2RTTStartup) { |
| CreateDefaultSetup(); |
| |
| SetConnectionOption(k2RTT); |
| EXPECT_EQ(2u, sender_->num_startup_rtts()); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| QuicRoundTripCount max_bw_round = 0; |
| QuicBandwidth max_bw(QuicBandwidth::Zero()); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this, &max_bw, &max_bw_round]() { |
| if (max_bw < sender_->ExportDebugState().max_bandwidth) { |
| max_bw = sender_->ExportDebugState().max_bandwidth; |
| max_bw_round = sender_->ExportDebugState().round_trip_count; |
| } |
| return sender_->ExportDebugState().is_at_full_bandwidth; |
| }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(2u, sender_->ExportDebugState().round_trip_count - max_bw_round); |
| EXPECT_EQ(2u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| // Test exiting STARTUP earlier upon loss. |
| TEST_F(BbrSenderTest, SimpleTransferExitStartupOnLoss) { |
| CreateDefaultSetup(); |
| |
| EXPECT_EQ(3u, sender_->num_startup_rtts()); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| QuicRoundTripCount max_bw_round = 0; |
| QuicBandwidth max_bw(QuicBandwidth::Zero()); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this, &max_bw, &max_bw_round]() { |
| if (max_bw < sender_->ExportDebugState().max_bandwidth) { |
| max_bw = sender_->ExportDebugState().max_bandwidth; |
| max_bw_round = sender_->ExportDebugState().round_trip_count; |
| } |
| return sender_->ExportDebugState().is_at_full_bandwidth; |
| }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(3u, sender_->ExportDebugState().round_trip_count - max_bw_round); |
| EXPECT_EQ(3u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| // Test exiting STARTUP earlier upon loss with a small buffer. |
| TEST_F(BbrSenderTest, SimpleTransferExitStartupOnLossSmallBuffer) { |
| CreateSmallBufferSetup(); |
| |
| EXPECT_EQ(3u, sender_->num_startup_rtts()); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| QuicRoundTripCount max_bw_round = 0; |
| QuicBandwidth max_bw(QuicBandwidth::Zero()); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this, &max_bw, &max_bw_round]() { |
| if (max_bw < sender_->ExportDebugState().max_bandwidth) { |
| max_bw = sender_->ExportDebugState().max_bandwidth; |
| max_bw_round = sender_->ExportDebugState().round_trip_count; |
| } |
| return sender_->ExportDebugState().is_at_full_bandwidth; |
| }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_GE(2u, sender_->ExportDebugState().round_trip_count - max_bw_round); |
| EXPECT_EQ(1u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_NE(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| TEST_F(BbrSenderTest, DerivedPacingGainStartup) { |
| CreateDefaultSetup(); |
| |
| SetConnectionOption(kBBQ1); |
| EXPECT_EQ(3u, sender_->num_startup_rtts()); |
| // Verify that Sender is in slow start. |
| EXPECT_TRUE(sender_->InSlowStart()); |
| // Verify that pacing rate is based on the initial RTT. |
| QuicBandwidth expected_pacing_rate = QuicBandwidth::FromBytesAndTimeDelta( |
| 2.773 * kDefaultWindowTCP, rtt_stats_->initial_rtt()); |
| EXPECT_APPROX_EQ(expected_pacing_rate.ToBitsPerSecond(), |
| sender_->PacingRate(0).ToBitsPerSecond(), 0.01f); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().is_at_full_bandwidth; }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(3u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| TEST_F(BbrSenderTest, DerivedCWNDGainStartup) { |
| CreateSmallBufferSetup(); |
| |
| EXPECT_EQ(3u, sender_->num_startup_rtts()); |
| // Verify that Sender is in slow start. |
| EXPECT_TRUE(sender_->InSlowStart()); |
| // Verify that pacing rate is based on the initial RTT. |
| QuicBandwidth expected_pacing_rate = QuicBandwidth::FromBytesAndTimeDelta( |
| 2.885 * kDefaultWindowTCP, rtt_stats_->initial_rtt()); |
| EXPECT_APPROX_EQ(expected_pacing_rate.ToBitsPerSecond(), |
| sender_->PacingRate(0).ToBitsPerSecond(), 0.01f); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().is_at_full_bandwidth; }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| if (!bbr_sender_.connection()->GetStats().bbr_exit_startup_due_to_loss) { |
| EXPECT_EQ(3u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| } |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| float loss_rate = |
| static_cast<float>(bbr_sender_.connection()->GetStats().packets_lost) / |
| bbr_sender_.connection()->GetStats().packets_sent; |
| EXPECT_LT(loss_rate, 0.15f); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| // Expect an SRTT less than 2.7 * Min RTT on exit from STARTUP. |
| EXPECT_GT(kTestRtt * 2.7, rtt_stats_->smoothed_rtt()); |
| } |
| |
| TEST_F(BbrSenderTest, AckAggregationInStartup) { |
| CreateDefaultSetup(); |
| |
| SetConnectionOption(kBBQ3); |
| EXPECT_EQ(3u, sender_->num_startup_rtts()); |
| // Verify that Sender is in slow start. |
| EXPECT_TRUE(sender_->InSlowStart()); |
| // Verify that pacing rate is based on the initial RTT. |
| QuicBandwidth expected_pacing_rate = QuicBandwidth::FromBytesAndTimeDelta( |
| 2.885 * kDefaultWindowTCP, rtt_stats_->initial_rtt()); |
| EXPECT_APPROX_EQ(expected_pacing_rate.ToBitsPerSecond(), |
| sender_->PacingRate(0).ToBitsPerSecond(), 0.01f); |
| |
| // Run until the full bandwidth is reached and check how many rounds it was. |
| bbr_sender_.AddBytesToTransfer(12 * 1024 * 1024); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return sender_->ExportDebugState().is_at_full_bandwidth; }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::DRAIN, sender_->ExportDebugState().mode); |
| EXPECT_EQ(3u, sender_->ExportDebugState().rounds_without_bandwidth_gain); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, |
| sender_->ExportDebugState().max_bandwidth, 0.01f); |
| EXPECT_EQ(0u, bbr_sender_.connection()->GetStats().packets_lost); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| } |
| |
| // Test that two BBR flows started slightly apart from each other terminate. |
| TEST_F(BbrSenderTest, SimpleCompetition) { |
| const QuicByteCount transfer_size = 10 * 1024 * 1024; |
| const QuicTime::Delta transfer_time = |
| kTestLinkBandwidth.TransferTime(transfer_size); |
| CreateBbrVsBbrSetup(); |
| |
| // Transfer 10% of data in first transfer. |
| bbr_sender_.AddBytesToTransfer(transfer_size); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return receiver_.bytes_received() >= 0.1 * transfer_size; }, |
| transfer_time); |
| ASSERT_TRUE(simulator_result); |
| |
| // Start the second transfer and wait until both finish. |
| competing_sender_.AddBytesToTransfer(transfer_size); |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return receiver_.bytes_received() == transfer_size && |
| competing_receiver_.bytes_received() == transfer_size; |
| }, |
| 3 * transfer_time); |
| ASSERT_TRUE(simulator_result); |
| } |
| |
| // Test that BBR can resume bandwidth from cached network parameters. |
| TEST_F(BbrSenderTest, ResumeConnectionState) { |
| CreateDefaultSetup(); |
| |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(kTestLinkBandwidth, kTestRtt, |
| false)); |
| EXPECT_EQ(kTestLinkBandwidth * kTestRtt, |
| sender_->ExportDebugState().congestion_window); |
| |
| EXPECT_EQ(kTestLinkBandwidth, sender_->PacingRate(/*bytes_in_flight=*/0)); |
| |
| EXPECT_APPROX_EQ(kTestRtt, sender_->ExportDebugState().min_rtt, 0.01f); |
| |
| DriveOutOfStartup(); |
| } |
| |
| // Test with a min CWND of 1 instead of 4 packets. |
| TEST_F(BbrSenderTest, ProbeRTTMinCWND1) { |
| CreateDefaultSetup(); |
| SetConnectionOption(kMIN1); |
| DriveOutOfStartup(); |
| |
| // We have no intention of ever finishing this transfer. |
| bbr_sender_.AddBytesToTransfer(100 * 1024 * 1024); |
| |
| // Wait until the connection enters PROBE_RTT. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(12); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { |
| return sender_->ExportDebugState().mode == BbrSender::PROBE_RTT; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| ASSERT_EQ(BbrSender::PROBE_RTT, sender_->ExportDebugState().mode); |
| // The PROBE_RTT CWND should be 1 if the min CWND is 1. |
| EXPECT_EQ(kDefaultTCPMSS, sender_->GetCongestionWindow()); |
| |
| // Exit PROBE_RTT. |
| const QuicTime probe_rtt_start = clock_->Now(); |
| const QuicTime::Delta time_to_exit_probe_rtt = |
| kTestRtt + QuicTime::Delta::FromMilliseconds(200); |
| simulator_.RunFor(1.5 * time_to_exit_probe_rtt); |
| EXPECT_EQ(BbrSender::PROBE_BW, sender_->ExportDebugState().mode); |
| EXPECT_GE(sender_->ExportDebugState().min_rtt_timestamp, probe_rtt_start); |
| } |
| |
| TEST_F(BbrSenderTest, StartupStats) { |
| CreateDefaultSetup(); |
| |
| DriveOutOfStartup(); |
| ASSERT_FALSE(sender_->InSlowStart()); |
| |
| const QuicConnectionStats& stats = bbr_sender_.connection()->GetStats(); |
| EXPECT_EQ(1u, stats.slowstart_count); |
| EXPECT_THAT(stats.slowstart_num_rtts, AllOf(Ge(5u), Le(15u))); |
| EXPECT_THAT(stats.slowstart_packets_sent, AllOf(Ge(100u), Le(1000u))); |
| EXPECT_THAT(stats.slowstart_bytes_sent, AllOf(Ge(100000u), Le(1000000u))); |
| EXPECT_LE(stats.slowstart_packets_lost, 10u); |
| EXPECT_LE(stats.slowstart_bytes_lost, 10000u); |
| EXPECT_FALSE(stats.slowstart_duration.IsRunning()); |
| EXPECT_THAT(stats.slowstart_duration.GetTotalElapsedTime(), |
| AllOf(Ge(QuicTime::Delta::FromMilliseconds(500)), |
| Le(QuicTime::Delta::FromMilliseconds(1500)))); |
| EXPECT_EQ(stats.slowstart_duration.GetTotalElapsedTime(), |
| QuicConnectionPeer::GetSentPacketManager(bbr_sender_.connection()) |
| ->GetSlowStartDuration()); |
| } |
| |
| // Regression test for b/143540157. |
| TEST_F(BbrSenderTest, RecalculatePacingRateOnCwndChange1RTT) { |
| CreateDefaultSetup(); |
| |
| bbr_sender_.AddBytesToTransfer(1 * 1024 * 1024); |
| // Wait until an ACK comes back. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| const QuicByteCount previous_cwnd = |
| sender_->ExportDebugState().congestion_window; |
| |
| // Bootstrap cwnd. |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(kTestLinkBandwidth, |
| QuicTime::Delta::Zero(), false)); |
| EXPECT_LT(previous_cwnd, sender_->ExportDebugState().congestion_window); |
| |
| // Verify pacing rate is re-calculated based on the new cwnd and min_rtt. |
| EXPECT_APPROX_EQ(QuicBandwidth::FromBytesAndTimeDelta( |
| sender_->ExportDebugState().congestion_window, |
| sender_->ExportDebugState().min_rtt), |
| sender_->PacingRate(/*bytes_in_flight=*/0), 0.01f); |
| } |
| |
| TEST_F(BbrSenderTest, RecalculatePacingRateOnCwndChange0RTT) { |
| CreateDefaultSetup(); |
| // Initial RTT is available. |
| const_cast<RttStats*>(rtt_stats_)->set_initial_rtt(kTestRtt); |
| |
| // Bootstrap cwnd. |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(kTestLinkBandwidth, |
| QuicTime::Delta::Zero(), false)); |
| EXPECT_LT(kInitialCongestionWindowPackets * kDefaultTCPMSS, |
| sender_->ExportDebugState().congestion_window); |
| // No Rtt sample is available. |
| EXPECT_TRUE(sender_->ExportDebugState().min_rtt.IsZero()); |
| |
| // Verify pacing rate is re-calculated based on the new cwnd and initial |
| // RTT. |
| EXPECT_APPROX_EQ(QuicBandwidth::FromBytesAndTimeDelta( |
| sender_->ExportDebugState().congestion_window, |
| rtt_stats_->initial_rtt()), |
| sender_->PacingRate(/*bytes_in_flight=*/0), 0.01f); |
| } |
| |
| TEST_F(BbrSenderTest, MitigateCwndBootstrappingOvershoot) { |
| CreateDefaultSetup(); |
| bbr_sender_.AddBytesToTransfer(1 * 1024 * 1024); |
| |
| // Wait until an ACK comes back. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Bootstrap cwnd by a overly large bandwidth sample. |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(8 * kTestLinkBandwidth, |
| QuicTime::Delta::Zero(), false)); |
| QuicBandwidth pacing_rate = sender_->PacingRate(0); |
| EXPECT_EQ(8 * kTestLinkBandwidth, pacing_rate); |
| |
| // Wait until pacing_rate decreases. |
| simulator_result = simulator_.RunUntilOrTimeout( |
| [this, pacing_rate]() { return sender_->PacingRate(0) < pacing_rate; }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| EXPECT_EQ(BbrSender::STARTUP, sender_->ExportDebugState().mode); |
| if (GetQuicReloadableFlag(quic_conservative_cwnd_and_pacing_gains)) { |
| EXPECT_APPROX_EQ(2.0f * sender_->BandwidthEstimate(), |
| sender_->PacingRate(0), 0.01f); |
| } else { |
| EXPECT_APPROX_EQ(2.885f * sender_->BandwidthEstimate(), |
| sender_->PacingRate(0), 0.01f); |
| } |
| } |
| |
| TEST_F(BbrSenderTest, 200InitialCongestionWindowWithNetworkParameterAdjusted) { |
| CreateDefaultSetup(); |
| |
| bbr_sender_.AddBytesToTransfer(1 * 1024 * 1024); |
| // Wait until an ACK comes back. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Bootstrap cwnd by a overly large bandwidth sample. |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(1024 * kTestLinkBandwidth, |
| QuicTime::Delta::Zero(), false)); |
| // Verify cwnd is capped at 200. |
| EXPECT_EQ(200 * kDefaultTCPMSS, |
| sender_->ExportDebugState().congestion_window); |
| EXPECT_GT(1024 * kTestLinkBandwidth, sender_->PacingRate(0)); |
| } |
| |
| TEST_F(BbrSenderTest, 100InitialCongestionWindowFromNetworkParameter) { |
| CreateDefaultSetup(); |
| |
| bbr_sender_.AddBytesToTransfer(1 * 1024 * 1024); |
| // Wait until an ACK comes back. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Bootstrap cwnd by a overly large bandwidth sample. |
| SendAlgorithmInterface::NetworkParams network_params( |
| 1024 * kTestLinkBandwidth, QuicTime::Delta::Zero(), false); |
| network_params.max_initial_congestion_window = 100; |
| bbr_sender_.connection()->AdjustNetworkParameters(network_params); |
| // Verify cwnd is capped at 100. |
| EXPECT_EQ(100 * kDefaultTCPMSS, |
| sender_->ExportDebugState().congestion_window); |
| EXPECT_GT(1024 * kTestLinkBandwidth, sender_->PacingRate(0)); |
| } |
| |
| TEST_F(BbrSenderTest, 100InitialCongestionWindowWithNetworkParameterAdjusted) { |
| SetConnectionOption(kICW1); |
| CreateDefaultSetup(); |
| |
| bbr_sender_.AddBytesToTransfer(1 * 1024 * 1024); |
| // Wait until an ACK comes back. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [this]() { return !sender_->ExportDebugState().min_rtt.IsZero(); }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Bootstrap cwnd by a overly large bandwidth sample. |
| bbr_sender_.connection()->AdjustNetworkParameters( |
| SendAlgorithmInterface::NetworkParams(1024 * kTestLinkBandwidth, |
| QuicTime::Delta::Zero(), false)); |
| // Verify cwnd is capped at 100. |
| EXPECT_EQ(100 * kDefaultTCPMSS, |
| sender_->ExportDebugState().congestion_window); |
| EXPECT_GT(1024 * kTestLinkBandwidth, sender_->PacingRate(0)); |
| } |
| |
| // Ensures bandwidth estimate does not change after a loss only event. |
| // Regression test for b/151239871. |
| TEST_F(BbrSenderTest, LossOnlyCongestionEvent) { |
| CreateDefaultSetup(); |
| |
| DriveOutOfStartup(); |
| EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited); |
| |
| // Send some bursts, each burst increments round count by 1, since it only |
| // generates small, app-limited samples, the max_bandwidth_ will not be |
| // updated. At the end of all bursts, all estimates in max_bandwidth_ will |
| // look very old such that any Update() will reset all estimates. |
| SendBursts(20, 512, QuicTime::Delta::FromSeconds(3)); |
| |
| QuicUnackedPacketMap* unacked_packets = |
| QuicSentPacketManagerPeer::GetUnackedPacketMap( |
| QuicConnectionPeer::GetSentPacketManager(bbr_sender_.connection())); |
| // Run until we have something in flight. |
| bbr_sender_.AddBytesToTransfer(50 * 1024 * 1024); |
| bool simulator_result = simulator_.RunUntilOrTimeout( |
| [&]() { return unacked_packets->bytes_in_flight() > 0; }, |
| QuicTime::Delta::FromSeconds(5)); |
| ASSERT_TRUE(simulator_result); |
| |
| const QuicBandwidth prior_bandwidth_estimate = sender_->BandwidthEstimate(); |
| EXPECT_APPROX_EQ(kTestLinkBandwidth, prior_bandwidth_estimate, 0.01f); |
| |
| // Lose the least unacked packet. |
| LostPacketVector lost_packets; |
| lost_packets.emplace_back( |
| bbr_sender_.connection()->sent_packet_manager().GetLeastUnacked(), |
| kDefaultMaxPacketSize); |
| |
| QuicTime now = simulator_.GetClock()->Now() + kTestRtt * 0.25; |
| sender_->OnCongestionEvent(false, unacked_packets->bytes_in_flight(), now, {}, |
| lost_packets); |
| |
| // Bandwidth estimate should not change for the loss only event. |
| EXPECT_EQ(prior_bandwidth_estimate, sender_->BandwidthEstimate()); |
| } |
| |
| TEST_F(BbrSenderTest, EnableOvershootingDetection) { |
| SetConnectionOption(kDTOS); |
| CreateSmallBufferSetup(); |
| // Set a overly large initial cwnd. |
| sender_->SetInitialCongestionWindowInPackets(200); |
| const QuicConnectionStats& stats = bbr_sender_.connection()->GetStats(); |
| EXPECT_FALSE(stats.overshooting_detected_with_network_parameters_adjusted); |
| DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(30)); |
| |
| // Verify overshooting is detected. |
| EXPECT_TRUE(stats.overshooting_detected_with_network_parameters_adjusted); |
| } |
| |
| } // namespace test |
| } // namespace quic |