| /* |
| * Copyright 2025 The WebRTC Project Authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| #include "modules/congestion_controller/scream/scream_v2.h" |
| |
| #include <algorithm> |
| |
| #include "api/environment/environment.h" |
| #include "api/transport/ecn_marking.h" |
| #include "api/transport/network_types.h" |
| #include "api/units/data_rate.h" |
| #include "api/units/data_size.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "modules/congestion_controller/scream/test/cc_feedback_generator.h" |
| #include "rtc_base/logging.h" |
| #include "system_wrappers/include/clock.h" |
| #include "test/create_test_environment.h" |
| #include "test/gtest.h" |
| #include "test/network/simulated_network.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| using ::testing::TestWithParam; |
| |
| constexpr DataSize kPacketSize = DataSize::Bytes(1000); |
| |
| TransportPacketsFeedback CreateFeedback(Timestamp feedback_time, |
| TimeDelta rtt, |
| int number_of_ect1_packets, |
| int number_of_packets_in_flight) { |
| int sequence_number = 0; |
| TransportPacketsFeedback feedback; |
| feedback.feedback_time = feedback_time; |
| Timestamp send_time = feedback_time - rtt; |
| |
| feedback.data_in_flight = kPacketSize * number_of_packets_in_flight; |
| |
| for (int i = 0; i < number_of_ect1_packets; ++i) { |
| PacketResult result; |
| result.sent_packet.send_time = send_time; |
| result.sent_packet.size = kPacketSize; |
| result.ecn = EcnMarking::kEct1; |
| result.receive_time = send_time + rtt / 2; |
| result.sent_packet.sequence_number = sequence_number++; |
| feedback.packet_feedbacks.push_back(result); |
| } |
| |
| return feedback; |
| } |
| |
| TEST(ScreamV2Test, TargetRateIncreaseToMaxOnUnConstrainedNetwork) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream(env); |
| const DataRate kMaxDataRate = DataRate::KilobitsPerSec(2000); |
| scream.SetTargetBitrateConstraints(DataRate::Zero(), kMaxDataRate); |
| DataRate send_rate = DataRate::KilobitsPerSec(100); |
| // Configure a feedback generator simulating a network with infinite |
| // capacity but 25ms one way delay. |
| CcFeedbackGenerator feedback_generator( |
| {.network_config = {.queue_delay_ms = 25}}); |
| |
| for (int i = 0; i < 100; ++i) { |
| TransportPacketsFeedback feedback = |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); |
| scream.OnTransportPacketsFeedback(feedback); |
| send_rate = scream.target_rate(); |
| } |
| EXPECT_EQ(send_rate, kMaxDataRate); |
| } |
| |
| TEST(ScreamV2Test, |
| ReferenceWindowDoesNotDecreaseAfterLowerSendRateOnUnconstrainedNetwork) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream(env); |
| const DataRate kMaxDataRate = DataRate::KilobitsPerSec(2000); |
| scream.SetTargetBitrateConstraints(DataRate::Zero(), kMaxDataRate); |
| DataRate send_rate = DataRate::KilobitsPerSec(100); |
| // Configure a feedback generator simulating a network with infinite |
| // capacity but 25ms one way delay. |
| CcFeedbackGenerator feedback_generator( |
| {.network_config = {.queue_delay_ms = 25}}); |
| |
| for (int i = 0; i < 70; ++i) { |
| TransportPacketsFeedback feedback = |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); |
| scream.OnTransportPacketsFeedback(feedback); |
| send_rate = scream.target_rate(); |
| } |
| DataSize ref_window = scream.ref_window(); |
| |
| // Half the send rate, but the network is still unconstrained. |
| send_rate = send_rate / 2; |
| for (int i = 0; i < 20; ++i) { |
| TransportPacketsFeedback feedback = |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); |
| scream.OnTransportPacketsFeedback(feedback); |
| } |
| // Still the same ref_window. |
| EXPECT_EQ(ref_window, scream.ref_window()); |
| } |
| |
| TEST(ScreamV2Test, ReferenceWindowIncreaseLessPerStepOnLowRtt) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream_1(env); |
| ScreamV2 scream_2(env); |
| |
| TransportPacketsFeedback high_rtt_feedback = |
| CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(100), |
| /*number_of_ect1_packets=*/20, |
| /*number_of_packets_in_flight=*/20); |
| TransportPacketsFeedback low_rtt_feedback = |
| CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(1), |
| /*number_of_ect1_packets=*/20, |
| /*number_of_packets_in_flight=*/20); |
| |
| scream_1.OnTransportPacketsFeedback(high_rtt_feedback); |
| scream_2.OnTransportPacketsFeedback(low_rtt_feedback); |
| |
| EXPECT_GT(scream_1.ref_window(), scream_2.ref_window()); |
| } |
| |
| TEST(ScreamV2Test, ReferenceWindowIncreaseLessPerStepIfCeDetected) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream_1(env); |
| ScreamV2 scream_2(env); |
| |
| TransportPacketsFeedback feedback = |
| CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(10), |
| /*number_of_ect1_packets=*/20, |
| /*number_of_packets_in_flight=*/20); |
| |
| TransportPacketsFeedback ce_detected_feedback = feedback; |
| ce_detected_feedback.packet_feedbacks[0].ecn = EcnMarking::kCe; |
| |
| scream_1.OnTransportPacketsFeedback(feedback); |
| scream_2.OnTransportPacketsFeedback(ce_detected_feedback); |
| |
| EXPECT_GT(scream_1.ref_window(), scream_2.ref_window()); |
| } |
| |
| TEST(ScreamV2Test, ReferenceWindowIncreaseToDataInflight) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream(env); |
| |
| Timestamp start_time = clock.CurrentTime(); |
| TimeDelta feedback_interval = TimeDelta::Millis(25); |
| |
| TransportPacketsFeedback feedback = |
| CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(10), |
| /*number_of_ect1_packets=*/20, |
| /*number_of_packets_in_flight=*/10); |
| |
| while (clock.CurrentTime() < start_time + TimeDelta::Seconds(2)) { |
| feedback.feedback_time = clock.CurrentTime(); |
| scream.OnTransportPacketsFeedback(feedback); |
| clock.AdvanceTime(feedback_interval); |
| } |
| // Target rate can increase up to 1.1 * data_in_flight + Max Segment Size( |
| // default 1000 bytes) when no max target rate has been set. |
| EXPECT_EQ(scream.ref_window(), |
| 1.1 * feedback.data_in_flight + DataSize::Bytes(1000)); |
| } |
| |
| TEST(ScreamV2Test, CalculatesL4sAlpha) { |
| SimulatedClock clock(Timestamp::Seconds(1'234)); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| |
| ScreamV2 scream(env); |
| |
| Timestamp start_time = clock.CurrentTime(); |
| TimeDelta feedback_interval = TimeDelta::Millis(25); |
| |
| TransportPacketsFeedback feedback = |
| CreateFeedback(clock.CurrentTime(), /*rtt=*/TimeDelta::Millis(10), |
| /*number_of_ect1_packets=*/20, |
| /*number_of_packets_in_flight=*/20); |
| // CE mark 20% of packets. |
| for (int i = 0; i < 4; ++i) { |
| feedback.packet_feedbacks[i].ecn = EcnMarking::kCe; |
| } |
| |
| double l4s_alpha = scream.l4s_alpha(); |
| while (clock.CurrentTime() < start_time + TimeDelta::Seconds(2)) { |
| feedback.feedback_time = clock.CurrentTime(); |
| scream.OnTransportPacketsFeedback(feedback); |
| EXPECT_GT(scream.l4s_alpha(), l4s_alpha); |
| clock.AdvanceTime(feedback_interval); |
| } |
| |
| EXPECT_NEAR(scream.l4s_alpha(), 0.2, 0.01); |
| } |
| |
| struct AdaptsToLinkCapacityParams { |
| SimulatedNetwork::Config network_config; |
| bool send_as_ect1 = true; |
| TimeDelta adaption_time; |
| TimeDelta time_to_run_after_adaption_time = TimeDelta::Seconds(5); |
| }; |
| |
| struct AdaptsToLinkCapacityResult { |
| DataRate data_rate; |
| DataRate min_rate_after_adaption; |
| DataRate max_rate_after_adaption; |
| TimeDelta max_smoothed_rtt_after_adaptation = TimeDelta::Zero(); |
| }; |
| |
| AdaptsToLinkCapacityResult RunAdaptToLinkCapacityTest( |
| const AdaptsToLinkCapacityParams& params) { |
| AdaptsToLinkCapacityResult result; |
| const Timestamp kStartTime = Timestamp::Seconds(1'234); |
| SimulatedClock clock(kStartTime); |
| Environment env = CreateTestEnvironment({.time = &clock}); |
| ScreamV2 scream(env); |
| CcFeedbackGenerator feedback_generator( |
| {.network_config = params.network_config, |
| .send_as_ect1 = params.send_as_ect1, |
| .packet_size = DataSize::Bytes(255)}); |
| |
| DataRate send_rate = DataRate::KilobitsPerSec(100); |
| while (clock.CurrentTime() < kStartTime + params.adaption_time) { |
| TransportPacketsFeedback feedback = |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); |
| scream.OnTransportPacketsFeedback(feedback); |
| send_rate = scream.target_rate(); |
| } |
| result.data_rate = send_rate; |
| result.min_rate_after_adaption = send_rate; |
| result.max_rate_after_adaption = send_rate; |
| |
| Timestamp time_after_adaption = clock.CurrentTime(); |
| while (clock.CurrentTime() < |
| time_after_adaption + params.time_to_run_after_adaption_time) { |
| TransportPacketsFeedback feedback = |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); |
| scream.OnTransportPacketsFeedback(feedback); |
| send_rate = scream.target_rate(); |
| result.min_rate_after_adaption = |
| std::min(result.min_rate_after_adaption, send_rate); |
| result.max_rate_after_adaption = |
| std::max(result.max_rate_after_adaption, send_rate); |
| result.max_smoothed_rtt_after_adaptation = |
| std::max(result.max_smoothed_rtt_after_adaptation, scream.rtt()); |
| } |
| RTC_LOG(LS_INFO) << " rate_after_adaption " << result.data_rate |
| << " max_rate_after_adaption: " |
| << result.max_rate_after_adaption |
| << " min_rate_after_adaption: " |
| << result.min_rate_after_adaption; |
| return result; |
| } |
| |
| TEST(ScreamV2Test, AdaptsToEcnLinkCapacity1Mbps) { |
| AdaptsToLinkCapacityParams params{ |
| .network_config = {.queue_delay_ms = 25, |
| .link_capacity = DataRate::KilobitsPerSec(1000)}, |
| .send_as_ect1 = true, |
| .adaption_time = TimeDelta::Seconds(4)}; |
| AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); |
| |
| EXPECT_LT(result.data_rate, DataRate::KilobitsPerSec(1100)); |
| EXPECT_GT(result.data_rate, DataRate::KilobitsPerSec(650)); |
| EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(1100)); |
| EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(650)); |
| |
| EXPECT_LT(result.max_smoothed_rtt_after_adaptation, |
| TimeDelta::Millis(25 * 2 + 25)); |
| } |
| |
| TEST(ScreamV2Test, AdaptsToLossLinkCapacity5Mbps) { |
| AdaptsToLinkCapacityParams params{ |
| .network_config = {.queue_length_packets = 3, |
| .queue_delay_ms = 10, |
| .link_capacity = DataRate::KilobitsPerSec(5000)}, |
| .send_as_ect1 = false, // Adapt only due to loss when queues overflow. |
| .adaption_time = TimeDelta::Seconds(10)}; |
| |
| AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); |
| |
| EXPECT_LT(result.data_rate, DataRate::KilobitsPerSec(5400)); |
| EXPECT_GT(result.data_rate, DataRate::KilobitsPerSec(1500)); |
| EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(5400)); |
| EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1500)); |
| |
| EXPECT_LT(result.max_smoothed_rtt_after_adaptation, |
| TimeDelta::Millis(10 * 2 + 40)); |
| } |
| |
| TEST(ScreamV2Test, AdaptsToDelayLinkCapacity2Mbps) { |
| AdaptsToLinkCapacityParams params{ |
| .network_config = {.queue_delay_ms = 10, |
| .link_capacity = DataRate::KilobitsPerSec(2000)}, |
| .send_as_ect1 = false, // Adapt only due to delay increase. |
| .adaption_time = TimeDelta::Seconds(10)}; |
| |
| AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); |
| |
| EXPECT_LT(result.data_rate, DataRate::KilobitsPerSec(2500)); |
| EXPECT_GT(result.data_rate, DataRate::KilobitsPerSec(1500)); |
| EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(2500)); |
| EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1500)); |
| |
| EXPECT_LT(result.max_smoothed_rtt_after_adaptation, |
| TimeDelta::Millis(10 * 2 + 50 + 10)); |
| } |
| |
| TEST(ScreamV2Test, AdaptsToDelayLinkCapacity2MbpsLongRunning) { |
| AdaptsToLinkCapacityParams params{ |
| .network_config = {.queue_delay_ms = 10, |
| .link_capacity = DataRate::KilobitsPerSec(2000)}, |
| .send_as_ect1 = false, // Adapt only due to delay increase. |
| .adaption_time = TimeDelta::Seconds(5), |
| .time_to_run_after_adaption_time = TimeDelta::Minutes(15)}; |
| |
| AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); |
| EXPECT_LT(result.max_smoothed_rtt_after_adaptation, |
| TimeDelta::Millis(10 * 2 + 60)); |
| } |
| |
| } // namespace |
| } // namespace webrtc |