blob: c657717554592a9c755d14602144ee8d2c2d0cd0 [file] [log] [blame]
/*
* 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 smoothed_rtt,
int number_of_ect1_packets,
int number_of_packets_in_flight) {
int sequence_number = 0;
TransportPacketsFeedback feedback;
feedback.feedback_time = feedback_time;
feedback.smoothed_rtt = smoothed_rtt;
Timestamp send_time = feedback_time - smoothed_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;
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);
send_rate = scream.OnTransportPacketsFeedback(feedback);
}
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);
send_rate = scream.OnTransportPacketsFeedback(feedback);
}
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 feedback =
CreateFeedback(clock.CurrentTime(), /*smoothed_rtt=*/
TimeDelta::Millis(10),
/*number_of_ect1_packets=*/20,
/*number_of_packets_in_flight=*/20);
TransportPacketsFeedback high_rtt_feedback = feedback;
high_rtt_feedback.smoothed_rtt = TimeDelta::Millis(100);
TransportPacketsFeedback low_rtt_feedback = feedback;
low_rtt_feedback.smoothed_rtt = TimeDelta::Millis(1);
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(), /*smoothed_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, ReferenceWindowIncreaseTo2xDataInflight) {
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(), /*smoothed_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 2 * data_in_flight + Max Segment Size(
// default 1000 bytes) when no max target rate has been set.
EXPECT_EQ(scream.ref_window(),
2 * 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(), /*smoothed_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_after_adaption;
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(1000)});
DataRate send_rate = DataRate::KilobitsPerSec(100);
while (clock.CurrentTime() < kStartTime + params.adaption_time) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
send_rate = scream.OnTransportPacketsFeedback(feedback);
}
result.data_rate_after_adaption = 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);
send_rate = scream.OnTransportPacketsFeedback(feedback);
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, feedback.smoothed_rtt);
}
RTC_LOG(LS_INFO) << " rate_after_adaption " << result.data_rate_after_adaption
<< " 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(3)};
AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params);
EXPECT_LT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(1100));
EXPECT_GT(result.data_rate_after_adaption, 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 + 20));
}
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_after_adaption, DataRate::KilobitsPerSec(5200));
EXPECT_GT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(4000));
EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(5200));
EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(3500));
EXPECT_LT(result.max_smoothed_rtt_after_adaptation,
TimeDelta::Millis(10 * 2 + 40));
}
TEST(ScreamV2Test, AdaptsToDelayLinkCapacity2Mbps) {
// TODO: bugs.webrtc.org/447037083 - investigate why target rate and rtt vary
// much more if `queue_delay_ms` is set to 10ms.
AdaptsToLinkCapacityParams params{
.network_config = {.queue_delay_ms = 5,
.link_capacity = DataRate::KilobitsPerSec(2000)},
.send_as_ect1 = false, // Adapt only due to delay increase.
.adaption_time = TimeDelta::Seconds(3)};
AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params);
EXPECT_LT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(2100));
EXPECT_GT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(1700));
EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(2300));
EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1700));
EXPECT_LT(result.max_smoothed_rtt_after_adaptation,
TimeDelta::Millis(10 * 2 + 50 + 10));
}
// TODO: bugs.webrtc.org/447037083 - implement support for resetting base delay
// if a standing queue has been build up.
// https://github.com/EricssonResearch/scream/blob/master/code/ScreamV2Tx.cpp#L1127
TEST(ScreamV2Test, DISABLED_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(3),
.time_to_run_after_adaption_time = TimeDelta::Minutes(15)};
AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params);
EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(2300));
EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1700));
EXPECT_LT(result.max_smoothed_rtt_after_adaptation,
TimeDelta::Millis(10 * 2 + 50 + 10));
}
} // namespace
} // namespace webrtc