blob: d9a641d6a554bb4b6d2fcccd5568c0ffa9a8d451 [file]
/*
* 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/delay_based_congestion_control.h"
#include "api/environment/environment.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/scream_v2_parameters.h"
#include "modules/congestion_controller/scream/test/cc_feedback_generator.h"
#include "system_wrappers/include/clock.h"
#include "test/create_test_environment.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
TEST(DelayBasedCongestionControlTest, InfiniteQueueBeforeFirstFeedback) {
Environment env = CreateTestEnvironment();
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
EXPECT_EQ(delay_controller.queue_delay(), TimeDelta::PlusInfinity());
}
TEST(DelayBasedCongestionControlTest,
QueueDelayDoesNotIncreaseIfSendRateIsLow) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
// Note, network_config.queue_delay_ms is the one way delay in the simulation,
// not a delay caused by queues.
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(1000)},
});
// Sending at a rate below link capacity should not cause queue delay to
// increase.
for (int i = 0; i < 10; ++i) {
DataRate send_rate = DataRate::KilobitsPerSec(100);
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
EXPECT_EQ(delay_controller.rtt(), TimeDelta::Millis(58));
EXPECT_EQ(delay_controller.queue_delay(), TimeDelta::Millis(0));
EXPECT_FALSE(delay_controller.IsQueueDelayDetected());
}
}
TEST(DelayBasedCongestionControlTest, QueueDelayIncreaseIfSendRateIsHigh) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
// Note, network_config.queue_delay_ms is the one way delay in the simulation,
// not a delay caused by queues.
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(1000)},
});
for (int i = 0; i < 10; ++i) {
// Send faster than link capacity to build a queue.
DataRate send_rate = DataRate::KilobitsPerSec(2000);
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
}
EXPECT_GT(delay_controller.queue_delay(), TimeDelta::Millis(50));
EXPECT_TRUE(delay_controller.IsQueueDelayDetected());
}
TEST(DelayBasedCongestionControlTest, ReferenceWindowNotChangedOnLowDelay) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
// Note, network_config.queue_delay_ms is the one way delay in the simulation,
// not a delay caused by queues.
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(1000)},
});
DataRate send_rate = DataRate::KilobitsPerSec(500);
DataSize ref_window = send_rate * TimeDelta::Millis(50);
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
ASSERT_EQ(delay_controller.queue_delay(), TimeDelta::Millis(0));
EXPECT_EQ(delay_controller.UpdateReferenceWindow(
ref_window, /*ref_window_mss_ratio=*/1.0),
ref_window);
}
TEST(DelayBasedCongestionControlTest, ReferenceWindowDecreasedOnHighDelay) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
// Note, network_config.queue_delay_ms is the one way delay in the simulation,
// not a delay caused by queues.
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(1000)},
});
DataRate send_rate = DataRate::KilobitsPerSec(2000);
TimeDelta smoothed_rtt;
for (int i = 0; i < 10; ++i) {
// Send faster than link capacity to build a queue.
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
smoothed_rtt = delay_controller.rtt();
}
DataSize ref_window = send_rate * smoothed_rtt;
DataSize updated_ref_window = delay_controller.UpdateReferenceWindow(
ref_window, /*ref_window_mss_ratio=*/1.0);
EXPECT_LT(updated_ref_window, 0.98 * ref_window);
EXPECT_GE(updated_ref_window, 0.5 * ref_window);
}
TEST(DelayBasedCongestionControlTest, ReferenceWindowNotLowerThanSetMin) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(1000)},
});
DataRate send_rate = DataRate::KilobitsPerSec(2000);
delay_controller.SetMinDelayBasedBwe(send_rate);
TimeDelta smoothed_rtt;
for (int i = 0; i < 10; ++i) {
// Send faster than link capacity to build a queue.
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
smoothed_rtt = delay_controller.rtt();
}
DataSize ref_window = send_rate * smoothed_rtt;
// Despite the queue delay, the reference window will not be decreased to a
// value that would cause the target rate to be below the minimum.
DataSize updated_ref_window = delay_controller.UpdateReferenceWindow(
ref_window, /*ref_window_mss_ratio=*/1.0);
EXPECT_EQ(updated_ref_window, ref_window);
}
TEST(DelayBasedCongestionControlTest, ResetQueueDelay) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(100)},
});
Timestamp start_time = clock.CurrentTime();
ASSERT_EQ(delay_controller.queue_delay(), TimeDelta::PlusInfinity());
// Overuse the link during 1s.
TimeDelta last_smoothed_rtt;
while (clock.CurrentTime() < start_time + TimeDelta::Seconds(1)) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
DataRate::KilobitsPerSec(150), clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
last_smoothed_rtt = delay_controller.rtt();
}
TimeDelta queue_delay_before_reset = delay_controller.queue_delay();
ASSERT_GT(queue_delay_before_reset, TimeDelta::Zero());
ASSERT_LT(queue_delay_before_reset, TimeDelta::PlusInfinity());
delay_controller.ResetQueueDelay();
ASSERT_EQ(delay_controller.queue_delay(), TimeDelta::PlusInfinity());
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(DataRate::KilobitsPerSec(150),
clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
// RTT is still increasing or equal to the last feedback.
EXPECT_GE(delay_controller.rtt(), last_smoothed_rtt);
// But queue delay should be lower.
EXPECT_LT(delay_controller.queue_delay(), queue_delay_before_reset);
}
TEST(DelayBasedCongestionControlTest,
IsQueueDrainedInTimeReturnFalseIfLongOverUse) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller(
ScreamV2Parameters(env.field_trials()));
CcFeedbackGenerator feedback_generator({
.network_config = {.queue_delay_ms = 25,
.link_capacity = DataRate::KilobitsPerSec(100)},
});
Timestamp start_time = clock.CurrentTime();
while (clock.CurrentTime() < start_time + TimeDelta::Seconds(30) &&
delay_controller.IsQueueDrainedInTime(clock.CurrentTime())) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
DataRate::KilobitsPerSec(150), clock);
delay_controller.OnTransportPacketsFeedback(feedback, /*alr=*/false);
}
EXPECT_LT(clock.CurrentTime(), start_time + TimeDelta::Seconds(30));
EXPECT_GT(clock.CurrentTime(), start_time + TimeDelta::Seconds(10));
EXPECT_FALSE(delay_controller.IsQueueDrainedInTime(clock.CurrentTime()));
}
TEST(DelayBasedCongestionControlTest,
RefWindowScaleFactorDueToMinAverageQueueDelay) {
SimulatedClock clock(Timestamp::Seconds(1234));
Environment env = CreateTestEnvironment({.time = &clock});
ScreamV2Parameters params(env.field_trials());
DelayBasedCongestionControl delay_controller(params);
const TimeDelta kQueueDelyMinThreshold =
params.queue_delay_min_threshold.Get();
// Helper to establish queue delay.
// Assumes base delay is 100ms.
auto feed_packets = [&](TimeDelta qdelay, int count) {
for (int i = 0; i < count; ++i) {
clock.AdvanceTime(TimeDelta::Millis(10));
TransportPacketsFeedback msg;
msg.feedback_time = clock.CurrentTime();
PacketResult packet;
packet.receive_time = clock.CurrentTime();
packet.sent_packet.send_time =
clock.CurrentTime() - TimeDelta::Millis(100) - qdelay;
packet.sent_packet.sequence_number = i;
msg.packet_feedbacks.push_back(packet);
delay_controller.OnTransportPacketsFeedback(msg, /*alr=*/false);
}
};
// Establish base delay (100ms) with qdelay=0.
feed_packets(TimeDelta::Zero(), 100);
EXPECT_DOUBLE_EQ(
delay_controller.ref_window_scale_factor_due_to_avg_min_delay(), 1.0);
// Drive queue delay to kQueueDelyMinThreshold.
feed_packets(kQueueDelyMinThreshold + TimeDelta::Millis(10), 250);
EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
0.1, 0.01);
// Drive queue delay to `0.5 * kQueueDelyMinThreshold`.
// Expected: 0.1 + 1.2 * 0.5 = 0.7
feed_packets(kQueueDelyMinThreshold / 2, 250);
EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
0.7, 0.01);
// Drive queue delay to `0.25 * kQueueDelyMinThreshold`.
// Expected: 0.1 + 1.2 * 0.75 = 1.0.
feed_packets(kQueueDelyMinThreshold / 4, 250);
EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
1.0, 0.01);
}
TEST(DelayBasedCongestionControlTest,
RefWindowScaleFactorDueToLatencyDifference) {
SimulatedClock clock(Timestamp::Seconds(1234));
Environment env = CreateTestEnvironment({.time = &clock});
ScreamV2Parameters params(env.field_trials());
DelayBasedCongestionControl delay_controller(params);
const TimeDelta kLatencyThreshold = params.latency_diff_threshold.Get();
// Helper to establish latency differences within feedbacks.
// Assumes a base one-way delay of 100ms.
auto feed_packets = [&](TimeDelta latency_diff, int count) {
for (int i = 0; i < count; ++i) {
clock.AdvanceTime(TimeDelta::Millis(10));
TransportPacketsFeedback msg;
msg.feedback_time = clock.CurrentTime();
PacketResult packet1;
packet1.receive_time = clock.CurrentTime();
packet1.sent_packet.send_time =
clock.CurrentTime() - TimeDelta::Millis(100);
packet1.sent_packet.sequence_number = i * 2;
PacketResult packet2;
packet2.receive_time = clock.CurrentTime();
packet2.sent_packet.send_time =
clock.CurrentTime() - TimeDelta::Millis(100) - latency_diff;
packet2.sent_packet.sequence_number = i * 2 + 1;
msg.packet_feedbacks.push_back(packet1);
msg.packet_feedbacks.push_back(packet2);
delay_controller.OnTransportPacketsFeedback(msg, /*alr=*/false);
}
};
// Establish 0 latency difference.
feed_packets(TimeDelta::Zero(), 100);
EXPECT_DOUBLE_EQ(
delay_controller.ref_window_scale_factor_due_to_latency_difference(),
1.0);
// Drive to `kLatencyThreshold`.
feed_packets(kLatencyThreshold + TimeDelta::Millis(10), 250);
EXPECT_NEAR(
delay_controller.ref_window_scale_factor_due_to_latency_difference(), 0.1,
0.01);
// Drive to `0.5 * kLatencyThreshold`.
// Expected: 0.1 + 1.2 * 0.5 = 0.7
feed_packets(kLatencyThreshold / 2, 250);
EXPECT_NEAR(
delay_controller.ref_window_scale_factor_due_to_latency_difference(), 0.7,
0.01);
// Drive to `0.25 * kLatencyThreshold`.
// Expected: 0.1 + 1.2 * 0.75 = 1.0.
feed_packets(kLatencyThreshold / 4, 250);
EXPECT_NEAR(
delay_controller.ref_window_scale_factor_due_to_latency_difference(), 1.0,
0.01);
}
TEST(DelayBasedCongestionControlTest, RttDecaysSlowerInAlr) {
SimulatedClock clock(Timestamp::Seconds(1234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller_alr(
ScreamV2Parameters(env.field_trials()));
DelayBasedCongestionControl delay_controller_no_alr(
ScreamV2Parameters(env.field_trials()));
auto feed_feedback = [&](DelayBasedCongestionControl& controller,
TimeDelta rtt, bool alr) {
TransportPacketsFeedback msg;
msg.feedback_time = clock.CurrentTime();
PacketResult packet;
packet.receive_time = clock.CurrentTime();
packet.sent_packet.send_time = clock.CurrentTime() - rtt;
packet.sent_packet.sequence_number = 0;
msg.packet_feedbacks.push_back(packet);
controller.OnTransportPacketsFeedback(msg, alr);
};
// Establish initial smoothed RTT of 200ms.
feed_feedback(delay_controller_alr, TimeDelta::Millis(200), /*alr=*/false);
feed_feedback(delay_controller_no_alr, TimeDelta::Millis(200), /*alr=*/false);
EXPECT_EQ(delay_controller_alr.rtt(), TimeDelta::Millis(200));
EXPECT_EQ(delay_controller_no_alr.rtt(), TimeDelta::Millis(200));
// Advance time.
clock.AdvanceTime(TimeDelta::Millis(100));
// Send feedback with a lower RTT sample of 100ms.
feed_feedback(delay_controller_alr, TimeDelta::Millis(100), /*alr=*/true);
feed_feedback(delay_controller_no_alr, TimeDelta::Millis(100), /*alr=*/false);
// With ALR, RTT decay is slower, so the resulting smoothed RTT is higher.
EXPECT_GT(delay_controller_alr.rtt(), delay_controller_no_alr.rtt());
// Verify actual values.
// no_alr: 100 * 0.125 + 200 * 0.875 = 187.5ms
// alr: 100 * (1/128) + 200 * (127/128) = 199.21875ms
EXPECT_NEAR(delay_controller_no_alr.rtt().ms<double>(), 187.5, 0.1);
EXPECT_NEAR(delay_controller_alr.rtt().ms<double>(), 199.2, 0.1);
}
TEST(DelayBasedCongestionControlTest, RttIncreasesSlowerInAlr) {
SimulatedClock clock(Timestamp::Seconds(1234));
Environment env = CreateTestEnvironment({.time = &clock});
DelayBasedCongestionControl delay_controller_alr(
ScreamV2Parameters(env.field_trials()));
DelayBasedCongestionControl delay_controller_no_alr(
ScreamV2Parameters(env.field_trials()));
auto feed_feedback = [&](DelayBasedCongestionControl& controller,
TimeDelta rtt, bool alr) {
TransportPacketsFeedback msg;
msg.feedback_time = clock.CurrentTime();
PacketResult packet;
packet.receive_time = clock.CurrentTime();
packet.sent_packet.send_time = clock.CurrentTime() - rtt;
packet.sent_packet.sequence_number = 0;
msg.packet_feedbacks.push_back(packet);
controller.OnTransportPacketsFeedback(msg, alr);
};
// Establish initial smoothed RTT of 100ms.
feed_feedback(delay_controller_alr, TimeDelta::Millis(100), /*alr=*/false);
feed_feedback(delay_controller_no_alr, TimeDelta::Millis(100), /*alr=*/false);
EXPECT_EQ(delay_controller_alr.rtt(), TimeDelta::Millis(100));
EXPECT_EQ(delay_controller_no_alr.rtt(), TimeDelta::Millis(100));
// Advance time.
clock.AdvanceTime(TimeDelta::Millis(100));
// Send feedback with a higher RTT sample of 200ms.
feed_feedback(delay_controller_alr, TimeDelta::Millis(200), /*alr=*/true);
feed_feedback(delay_controller_no_alr, TimeDelta::Millis(200), /*alr=*/false);
// With ALR, RTT increase is slower, so the resulting smoothed RTT is lower.
EXPECT_LT(delay_controller_alr.rtt(), delay_controller_no_alr.rtt());
// Verify actual values.
// no_alr: 200 * 0.125 + 100 * 0.875 = 112.5ms
// alr: 200 * (1/128) + 100 * (127/128) = 100.78125ms
EXPECT_NEAR(delay_controller_no_alr.rtt().ms<double>(), 112.5, 0.1);
EXPECT_NEAR(delay_controller_alr.rtt().ms<double>(), 100.8, 0.1);
}
// TODO: bugs.webrtc.org/447037083 - add tests for clock drift in feedback NTP
// time.
} // namespace
} // namespace webrtc