From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../goog_cc/goog_cc_network_control_unittest.cc | 1047 ++++++++++++++++++++ 1 file changed, 1047 insertions(+) create mode 100644 third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc (limited to 'third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc') diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc new file mode 100644 index 0000000000..c97d34da22 --- /dev/null +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc @@ -0,0 +1,1047 @@ +/* + * Copyright (c) 2018 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 +#include +#include +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/test/network_emulation/create_cross_traffic.h" +#include "api/test/network_emulation/cross_traffic.h" +#include "api/transport/goog_cc_factory.h" +#include "api/transport/network_control.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 "call/video_receive_stream.h" +#include "logging/rtc_event_log/mock/mock_rtc_event_log.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/scenario.h" +#include "test/scenario/scenario_config.h" + +using ::testing::IsEmpty; +using ::testing::NiceMock; + +namespace webrtc { +namespace test { +namespace { +// Count dips from a constant high bandwidth level within a short window. +int CountBandwidthDips(std::queue bandwidth_history, + DataRate threshold) { + if (bandwidth_history.empty()) + return true; + DataRate first = bandwidth_history.front(); + bandwidth_history.pop(); + + int dips = 0; + bool state_high = true; + while (!bandwidth_history.empty()) { + if (bandwidth_history.front() + threshold < first && state_high) { + ++dips; + state_high = false; + } else if (bandwidth_history.front() == first) { + state_high = true; + } else if (bandwidth_history.front() > first) { + // If this is toggling we will catch it later when front becomes first. + state_high = false; + } + bandwidth_history.pop(); + } + return dips; +} +GoogCcNetworkControllerFactory CreateFeedbackOnlyFactory() { + GoogCcFactoryConfig config; + config.feedback_only = true; + return GoogCcNetworkControllerFactory(std::move(config)); +} + +const uint32_t kInitialBitrateKbps = 60; +const DataRate kInitialBitrate = DataRate::KilobitsPerSec(kInitialBitrateKbps); +const float kDefaultPacingRate = 2.5f; + +CallClient* CreateVideoSendingClient( + Scenario* s, + CallClientConfig config, + std::vector send_link, + std::vector return_link) { + auto* client = s->CreateClient("send", std::move(config)); + auto* route = s->CreateRoutes(client, send_link, + s->CreateClient("return", CallClientConfig()), + return_link); + s->CreateVideoStream(route->forward(), VideoStreamConfig()); + return client; +} + +NetworkRouteChange CreateRouteChange( + Timestamp time, + absl::optional start_rate = absl::nullopt, + absl::optional min_rate = absl::nullopt, + absl::optional max_rate = absl::nullopt) { + NetworkRouteChange route_change; + route_change.at_time = time; + route_change.constraints.at_time = time; + route_change.constraints.min_data_rate = min_rate; + route_change.constraints.max_data_rate = max_rate; + route_change.constraints.starting_rate = start_rate; + return route_change; +} + +PacketResult CreatePacketResult(Timestamp arrival_time, + Timestamp send_time, + size_t payload_size, + PacedPacketInfo pacing_info) { + PacketResult packet_result; + packet_result.sent_packet = SentPacket(); + packet_result.sent_packet.send_time = send_time; + packet_result.sent_packet.size = DataSize::Bytes(payload_size); + packet_result.sent_packet.pacing_info = pacing_info; + packet_result.receive_time = arrival_time; + return packet_result; +} + +// Simulate sending packets and receiving transport feedback during +// `runtime_ms`, then return the final target birate. +absl::optional PacketTransmissionAndFeedbackBlock( + NetworkControllerInterface* controller, + int64_t runtime_ms, + int64_t delay, + Timestamp& current_time) { + NetworkControlUpdate update; + absl::optional target_bitrate; + int64_t delay_buildup = 0; + int64_t start_time_ms = current_time.ms(); + while (current_time.ms() - start_time_ms < runtime_ms) { + constexpr size_t kPayloadSize = 1000; + PacketResult packet = + CreatePacketResult(current_time + TimeDelta::Millis(delay_buildup), + current_time, kPayloadSize, PacedPacketInfo()); + delay_buildup += delay; + update = controller->OnSentPacket(packet.sent_packet); + if (update.target_rate) { + target_bitrate = update.target_rate->target_rate; + } + TransportPacketsFeedback feedback; + feedback.feedback_time = packet.receive_time; + feedback.packet_feedbacks.push_back(packet); + update = controller->OnTransportPacketsFeedback(feedback); + if (update.target_rate) { + target_bitrate = update.target_rate->target_rate; + } + current_time += TimeDelta::Millis(50); + update = controller->OnProcessInterval({.at_time = current_time}); + if (update.target_rate) { + target_bitrate = update.target_rate->target_rate; + } + } + return target_bitrate; +} + +// Create transport packets feedback with a built-up delay. +TransportPacketsFeedback CreateTransportPacketsFeedback( + TimeDelta per_packet_network_delay, + TimeDelta one_way_delay, + Timestamp send_time) { + TimeDelta delay_buildup = one_way_delay; + constexpr int kFeedbackSize = 3; + constexpr size_t kPayloadSize = 1000; + TransportPacketsFeedback feedback; + for (int i = 0; i < kFeedbackSize; ++i) { + PacketResult packet = CreatePacketResult( + /*arrival_time=*/send_time + delay_buildup, send_time, kPayloadSize, + PacedPacketInfo()); + delay_buildup += per_packet_network_delay; + feedback.feedback_time = packet.receive_time + one_way_delay; + feedback.packet_feedbacks.push_back(packet); + } + return feedback; +} + +// Scenarios: + +void UpdatesTargetRateBasedOnLinkCapacity(absl::string_view test_name = "") { + auto factory = CreateFeedbackOnlyFactory(); + Scenario s("googcc_unit/target_capacity" + std::string(test_name), false); + CallClientConfig config; + config.transport.cc_factory = &factory; + config.transport.rates.min_rate = DataRate::KilobitsPerSec(10); + config.transport.rates.max_rate = DataRate::KilobitsPerSec(1500); + config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + auto send_net = s.CreateMutableSimulationNode([](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(500); + c->delay = TimeDelta::Millis(100); + c->loss_rate = 0.0; + }); + auto ret_net = s.CreateMutableSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); }); + StatesPrinter* truth = s.CreatePrinter( + "send.truth.txt", TimeDelta::PlusInfinity(), {send_net->ConfigPrinter()}); + + auto* client = CreateVideoSendingClient(&s, config, {send_net->node()}, + {ret_net->node()}); + + truth->PrintRow(); + s.RunFor(TimeDelta::Seconds(25)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate().kbps(), 450, 100); + + send_net->UpdateConfig([](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(800); + c->delay = TimeDelta::Millis(100); + }); + + truth->PrintRow(); + s.RunFor(TimeDelta::Seconds(20)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate().kbps(), 750, 150); + + send_net->UpdateConfig([](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(100); + c->delay = TimeDelta::Millis(200); + }); + ret_net->UpdateConfig( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(200); }); + + truth->PrintRow(); + s.RunFor(TimeDelta::Seconds(50)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate().kbps(), 90, 25); +} + +DataRate RunRembDipScenario(absl::string_view test_name) { + Scenario s(test_name); + NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(2000); + net_conf.delay = TimeDelta::Millis(50); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000); + }); + auto send_net = {s.CreateSimulationNode(net_conf)}; + auto ret_net = {s.CreateSimulationNode(net_conf)}; + auto* route = s.CreateRoutes( + client, send_net, s.CreateClient("return", CallClientConfig()), ret_net); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + + s.RunFor(TimeDelta::Seconds(10)); + EXPECT_GT(client->send_bandwidth().kbps(), 1500); + + DataRate RembLimit = DataRate::KilobitsPerSec(250); + client->SetRemoteBitrate(RembLimit); + s.RunFor(TimeDelta::Seconds(1)); + EXPECT_EQ(client->send_bandwidth(), RembLimit); + + DataRate RembLimitLifted = DataRate::KilobitsPerSec(10000); + client->SetRemoteBitrate(RembLimitLifted); + s.RunFor(TimeDelta::Seconds(10)); + + return client->send_bandwidth(); +} + +} // namespace + +class NetworkControllerTestFixture { + public: + NetworkControllerTestFixture() : factory_() {} + explicit NetworkControllerTestFixture(GoogCcFactoryConfig googcc_config) + : factory_(std::move(googcc_config)) {} + + std::unique_ptr CreateController() { + NetworkControllerConfig config = InitialConfig(); + std::unique_ptr controller = + factory_.Create(config); + return controller; + } + + private: + NetworkControllerConfig InitialConfig( + int starting_bandwidth_kbps = kInitialBitrateKbps, + int min_data_rate_kbps = 0, + int max_data_rate_kbps = 5 * kInitialBitrateKbps) { + NetworkControllerConfig config; + config.constraints.at_time = Timestamp::Zero(); + config.constraints.min_data_rate = + DataRate::KilobitsPerSec(min_data_rate_kbps); + config.constraints.max_data_rate = + DataRate::KilobitsPerSec(max_data_rate_kbps); + config.constraints.starting_rate = + DataRate::KilobitsPerSec(starting_bandwidth_kbps); + config.event_log = &event_log_; + return config; + } + + NiceMock event_log_; + GoogCcNetworkControllerFactory factory_; +}; + +TEST(GoogCcNetworkControllerTest, + InitializeTargetRateOnFirstProcessIntervalAfterNetworkAvailable) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = Timestamp::Millis(123456), .network_available = true}); + update = + controller->OnProcessInterval({.at_time = Timestamp::Millis(123456)}); + + EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate); + EXPECT_EQ(update.pacer_config->data_rate(), + kInitialBitrate * kDefaultPacingRate); + EXPECT_EQ(update.probe_cluster_configs[0].target_data_rate, + kInitialBitrate * 3); + EXPECT_EQ(update.probe_cluster_configs[1].target_data_rate, + kInitialBitrate * 5); +} + +TEST(GoogCcNetworkControllerTest, ReactsToChangedNetworkConditions) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + update = controller->OnProcessInterval({.at_time = current_time}); + update = controller->OnRemoteBitrateReport( + {.receive_time = current_time, .bandwidth = kInitialBitrate * 2}); + + current_time += TimeDelta::Millis(25); + update = controller->OnProcessInterval({.at_time = current_time}); + EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate * 2); + EXPECT_EQ(update.pacer_config->data_rate(), + kInitialBitrate * 2 * kDefaultPacingRate); + + update = controller->OnRemoteBitrateReport( + {.receive_time = current_time, .bandwidth = kInitialBitrate}); + current_time += TimeDelta::Millis(25); + update = controller->OnProcessInterval({.at_time = current_time}); + EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate); + EXPECT_EQ(update.pacer_config->data_rate(), + kInitialBitrate * kDefaultPacingRate); +} + +TEST(GoogCcNetworkControllerTest, OnNetworkRouteChanged) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + DataRate new_bitrate = DataRate::BitsPerSec(200000); + + update = controller->OnNetworkRouteChange( + CreateRouteChange(current_time, new_bitrate)); + EXPECT_EQ(update.target_rate->target_rate, new_bitrate); + EXPECT_EQ(update.pacer_config->data_rate(), new_bitrate * kDefaultPacingRate); + EXPECT_EQ(update.probe_cluster_configs.size(), 2u); + + // If the bitrate is reset to -1, the new starting bitrate will be + // the minimum default bitrate. + const DataRate kDefaultMinBitrate = DataRate::KilobitsPerSec(5); + update = controller->OnNetworkRouteChange(CreateRouteChange(current_time)); + EXPECT_EQ(update.target_rate->target_rate, kDefaultMinBitrate); + EXPECT_NEAR(update.pacer_config->data_rate().bps(), + kDefaultMinBitrate.bps() * kDefaultPacingRate, 10); + EXPECT_EQ(update.probe_cluster_configs.size(), 2u); +} + +TEST(GoogCcNetworkControllerTest, ProbeOnRouteChange) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + current_time += TimeDelta::Seconds(3); + + update = controller->OnNetworkRouteChange( + CreateRouteChange(current_time, 2 * kInitialBitrate, DataRate::Zero(), + 20 * kInitialBitrate)); + + EXPECT_TRUE(update.pacer_config.has_value()); + EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate * 2); + EXPECT_EQ(update.probe_cluster_configs.size(), 2u); + EXPECT_EQ(update.probe_cluster_configs[0].target_data_rate, + kInitialBitrate * 6); + EXPECT_EQ(update.probe_cluster_configs[1].target_data_rate, + kInitialBitrate * 12); + + update = controller->OnProcessInterval({.at_time = current_time}); +} + +TEST(GoogCcNetworkControllerTest, ProbeAfterRouteChangeWhenTransportWritable) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = false}); + EXPECT_THAT(update.probe_cluster_configs, IsEmpty()); + + update = controller->OnNetworkRouteChange( + CreateRouteChange(current_time, 2 * kInitialBitrate, DataRate::Zero(), + 20 * kInitialBitrate)); + // Transport is not writable. So not point in sending a probe. + EXPECT_THAT(update.probe_cluster_configs, IsEmpty()); + + // Probe is sent when transport becomes writable. + update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + EXPECT_THAT(update.probe_cluster_configs, Not(IsEmpty())); +} + +// Bandwidth estimation is updated when feedbacks are received. +// Feedbacks which show an increasing delay cause the estimation to be reduced. +TEST(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) { + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + const int64_t kRunTimeMs = 6000; + Timestamp current_time = Timestamp::Millis(123); + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + + // The test must run and insert packets/feedback long enough that the + // BWE computes a valid estimate. This is first done in an environment which + // simulates no bandwidth limitation, and therefore not built-up delay. + absl::optional target_bitrate_before_delay = + PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 0, + current_time); + ASSERT_TRUE(target_bitrate_before_delay.has_value()); + + // Repeat, but this time with a building delay, and make sure that the + // estimation is adjusted downwards. + absl::optional target_bitrate_after_delay = + PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 50, + current_time); + EXPECT_LT(*target_bitrate_after_delay, *target_bitrate_before_delay); +} + +TEST(GoogCcNetworkControllerTest, PaceAtMaxOfLowerLinkCapacityAndBwe) { + ScopedFieldTrials trial( + "WebRTC-Bwe-PaceAtMaxOfBweAndLowerLinkCapacity/Enabled/"); + NetworkControllerTestFixture fixture; + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + update = controller->OnProcessInterval({.at_time = current_time}); + current_time += TimeDelta::Millis(100); + NetworkStateEstimate network_estimate = {.link_capacity_lower = + 10 * kInitialBitrate}; + update = controller->OnNetworkStateEstimate(network_estimate); + // OnNetworkStateEstimate does not trigger processing a new estimate. So add a + // dummy loss report to trigger a BWE update in the next process interval. + TransportLossReport loss_report; + loss_report.start_time = current_time; + loss_report.end_time = current_time; + loss_report.receive_time = current_time; + loss_report.packets_received_delta = 50; + loss_report.packets_lost_delta = 1; + update = controller->OnTransportLossReport(loss_report); + update = controller->OnProcessInterval({.at_time = current_time}); + ASSERT_TRUE(update.pacer_config); + ASSERT_TRUE(update.target_rate); + ASSERT_LT(update.target_rate->target_rate, + network_estimate.link_capacity_lower); + EXPECT_EQ(update.pacer_config->data_rate().kbps(), + network_estimate.link_capacity_lower.kbps() * kDefaultPacingRate); + + current_time += TimeDelta::Millis(100); + // Set a low link capacity estimate and verify that pacing rate is set + // relative to loss based/delay based estimate. + network_estimate = {.link_capacity_lower = 0.5 * kInitialBitrate}; + update = controller->OnNetworkStateEstimate(network_estimate); + // Again, we need to inject a dummy loss report to trigger an update of the + // BWE in the next process interval. + loss_report.start_time = current_time; + loss_report.end_time = current_time; + loss_report.receive_time = current_time; + loss_report.packets_received_delta = 50; + loss_report.packets_lost_delta = 0; + update = controller->OnTransportLossReport(loss_report); + update = controller->OnProcessInterval({.at_time = current_time}); + ASSERT_TRUE(update.target_rate); + ASSERT_GT(update.target_rate->target_rate, + network_estimate.link_capacity_lower); + EXPECT_EQ(update.pacer_config->data_rate().kbps(), + update.target_rate->target_rate.kbps() * kDefaultPacingRate); +} + +// Test congestion window pushback on network delay happens. +TEST(GoogCcScenario, CongestionWindowPushbackOnNetworkDelay) { + auto factory = CreateFeedbackOnlyFactory(); + ScopedFieldTrials trial( + "WebRTC-CongestionWindow/QueueSize:800,MinBitrate:30000/"); + Scenario s("googcc_unit/cwnd_on_delay", false); + auto send_net = + s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(1000); + c->delay = TimeDelta::Millis(100); + }); + auto ret_net = s.CreateSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); }); + CallClientConfig config; + config.transport.cc_factory = &factory; + // Start high so bandwidth drop has max effect. + config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000); + config.transport.rates.min_rate = DataRate::KilobitsPerSec(10); + + auto* client = CreateVideoSendingClient(&s, std::move(config), + {send_net->node()}, {ret_net}); + + s.RunFor(TimeDelta::Seconds(10)); + send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(10)); + s.RunFor(TimeDelta::Seconds(3)); + + // After 3 seconds without feedback from any sent packets, we expect that the + // target rate is reduced to the minimum pushback threshold + // kDefaultMinPushbackTargetBitrateBps, which is defined as 30 kbps in + // congestion_window_pushback_controller. + EXPECT_LT(client->target_rate().kbps(), 40); +} + +// Test congestion window pushback on network delay happens. +TEST(GoogCcScenario, CongestionWindowPushbackDropFrameOnNetworkDelay) { + auto factory = CreateFeedbackOnlyFactory(); + ScopedFieldTrials trial( + "WebRTC-CongestionWindow/QueueSize:800,MinBitrate:30000,DropFrame:true/"); + Scenario s("googcc_unit/cwnd_on_delay", false); + auto send_net = + s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(1000); + c->delay = TimeDelta::Millis(100); + }); + auto ret_net = s.CreateSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); }); + CallClientConfig config; + config.transport.cc_factory = &factory; + // Start high so bandwidth drop has max effect. + config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000); + config.transport.rates.min_rate = DataRate::KilobitsPerSec(10); + + auto* client = CreateVideoSendingClient(&s, std::move(config), + {send_net->node()}, {ret_net}); + + s.RunFor(TimeDelta::Seconds(10)); + send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(10)); + s.RunFor(TimeDelta::Seconds(3)); + + // As the dropframe is set, after 3 seconds without feedback from any sent + // packets, we expect that the target rate is not reduced by congestion + // window. + EXPECT_GT(client->target_rate().kbps(), 300); +} + +TEST(GoogCcScenario, PaddingRateLimitedByCongestionWindowInTrial) { + ScopedFieldTrials trial( + "WebRTC-CongestionWindow/QueueSize:200,MinBitrate:30000/"); + + Scenario s("googcc_unit/padding_limited", false); + auto send_net = + s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(1000); + c->delay = TimeDelta::Millis(100); + }); + auto ret_net = s.CreateSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); }); + CallClientConfig config; + // Start high so bandwidth drop has max effect. + config.transport.rates.start_rate = DataRate::KilobitsPerSec(1000); + config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000); + auto* client = s.CreateClient("send", config); + auto* route = + s.CreateRoutes(client, {send_net->node()}, + s.CreateClient("return", CallClientConfig()), {ret_net}); + VideoStreamConfig video; + video.stream.pad_to_rate = config.transport.rates.max_rate; + s.CreateVideoStream(route->forward(), video); + + // Run for a few seconds to allow the controller to stabilize. + s.RunFor(TimeDelta::Seconds(10)); + + // Check that padding rate matches target rate. + EXPECT_NEAR(client->padding_rate().kbps(), client->target_rate().kbps(), 1); + + // Check this is also the case when congestion window pushback kicks in. + send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(1)); + EXPECT_NEAR(client->padding_rate().kbps(), client->target_rate().kbps(), 1); +} + +TEST(GoogCcScenario, LimitsToFloorIfRttIsHighInTrial) { + // The field trial limits maximum RTT to 2 seconds, higher RTT means that the + // controller backs off until it reaches the minimum configured bitrate. This + // allows the RTT to recover faster than the regular control mechanism would + // achieve. + const DataRate kBandwidthFloor = DataRate::KilobitsPerSec(50); + ScopedFieldTrials trial("WebRTC-Bwe-MaxRttLimit/limit:2s,floor:" + + std::to_string(kBandwidthFloor.kbps()) + "kbps/"); + // In the test case, we limit the capacity and add a cross traffic packet + // burst that blocks media from being sent. This causes the RTT to quickly + // increase above the threshold in the trial. + const DataRate kLinkCapacity = DataRate::KilobitsPerSec(100); + const TimeDelta kBufferBloatDuration = TimeDelta::Seconds(10); + Scenario s("googcc_unit/limit_trial", false); + auto send_net = s.CreateSimulationNode([=](NetworkSimulationConfig* c) { + c->bandwidth = kLinkCapacity; + c->delay = TimeDelta::Millis(100); + }); + auto ret_net = s.CreateSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); }); + CallClientConfig config; + config.transport.rates.start_rate = kLinkCapacity; + + auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net}); + // Run for a few seconds to allow the controller to stabilize. + s.RunFor(TimeDelta::Seconds(10)); + const DataSize kBloatPacketSize = DataSize::Bytes(1000); + const int kBloatPacketCount = + static_cast(kBufferBloatDuration * kLinkCapacity / kBloatPacketSize); + // This will cause the RTT to be large for a while. + s.TriggerPacketBurst({send_net}, kBloatPacketCount, kBloatPacketSize.bytes()); + // Wait to allow the high RTT to be detected and acted upon. + s.RunFor(TimeDelta::Seconds(6)); + // By now the target rate should have dropped to the minimum configured rate. + EXPECT_NEAR(client->target_rate().kbps(), kBandwidthFloor.kbps(), 5); +} + +TEST(GoogCcScenario, UpdatesTargetRateBasedOnLinkCapacity) { + UpdatesTargetRateBasedOnLinkCapacity(); +} + +TEST(GoogCcScenario, StableEstimateDoesNotVaryInSteadyState) { + auto factory = CreateFeedbackOnlyFactory(); + Scenario s("googcc_unit/stable_target", false); + CallClientConfig config; + config.transport.cc_factory = &factory; + NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(500); + net_conf.delay = TimeDelta::Millis(100); + auto send_net = s.CreateSimulationNode(net_conf); + auto ret_net = s.CreateSimulationNode(net_conf); + + auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net}); + // Run for a while to allow the estimate to stabilize. + s.RunFor(TimeDelta::Seconds(30)); + DataRate min_stable_target = DataRate::PlusInfinity(); + DataRate max_stable_target = DataRate::MinusInfinity(); + DataRate min_target = DataRate::PlusInfinity(); + DataRate max_target = DataRate::MinusInfinity(); + + // Measure variation in steady state. + for (int i = 0; i < 20; ++i) { + auto stable_target_rate = client->stable_target_rate(); + auto target_rate = client->target_rate(); + EXPECT_LE(stable_target_rate, target_rate); + + min_stable_target = std::min(min_stable_target, stable_target_rate); + max_stable_target = std::max(max_stable_target, stable_target_rate); + min_target = std::min(min_target, target_rate); + max_target = std::max(max_target, target_rate); + s.RunFor(TimeDelta::Seconds(1)); + } + // We should expect drops by at least 15% (default backoff.) + EXPECT_LT(min_target / max_target, 0.85); + // We should expect the stable target to be more stable than the immediate one + EXPECT_GE(min_stable_target / max_stable_target, min_target / max_target); +} + +TEST(GoogCcScenario, LossBasedControlUpdatesTargetRateBasedOnLinkCapacity) { + ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/"); + // TODO(srte): Should the behavior be unaffected at low loss rates? + UpdatesTargetRateBasedOnLinkCapacity("_loss_based"); +} + +TEST(GoogCcScenario, LossBasedControlDoesModestBackoffToHighLoss) { + ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/"); + Scenario s("googcc_unit/high_loss_channel", false); + CallClientConfig config; + config.transport.rates.min_rate = DataRate::KilobitsPerSec(10); + config.transport.rates.max_rate = DataRate::KilobitsPerSec(1500); + config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + auto send_net = s.CreateSimulationNode([](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(2000); + c->delay = TimeDelta::Millis(200); + c->loss_rate = 0.1; + }); + auto ret_net = s.CreateSimulationNode( + [](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(200); }); + + auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net}); + + s.RunFor(TimeDelta::Seconds(120)); + // Without LossBasedControl trial, bandwidth drops to ~10 kbps. + EXPECT_GT(client->target_rate().kbps(), 100); +} + +DataRate AverageBitrateAfterCrossInducedLoss(absl::string_view name) { + Scenario s(name, false); + NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(1000); + net_conf.delay = TimeDelta::Millis(100); + // Short queue length means that we'll induce loss when sudden TCP traffic + // spikes are induced. This corresponds to ca 200 ms for a packet size of 1000 + // bytes. Such limited buffers are common on for instance wifi routers. + net_conf.packet_queue_length_limit = 25; + + auto send_net = {s.CreateSimulationNode(net_conf)}; + auto ret_net = {s.CreateSimulationNode(net_conf)}; + + auto* client = s.CreateClient("send", CallClientConfig()); + auto* callee = s.CreateClient("return", CallClientConfig()); + auto* route = s.CreateRoutes(client, send_net, callee, ret_net); + // TODO(srte): Make this work with RTX enabled or remove it. + auto* video = s.CreateVideoStream(route->forward(), [](VideoStreamConfig* c) { + c->stream.use_rtx = false; + }); + s.RunFor(TimeDelta::Seconds(10)); + for (int i = 0; i < 4; ++i) { + // Sends TCP cross traffic inducing loss. + auto* tcp_traffic = s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic( + s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net), + FakeTcpConfig())); + s.RunFor(TimeDelta::Seconds(2)); + // Allow the ccongestion controller to recover. + s.net()->StopCrossTraffic(tcp_traffic); + s.RunFor(TimeDelta::Seconds(20)); + } + + // Querying the video stats from within the expected runtime environment + // (i.e. the TQ that belongs to the CallClient, not the Scenario TQ that + // we're currently on). + VideoReceiveStreamInterface::Stats video_receive_stats; + auto* video_stream = video->receive(); + callee->SendTask([&video_stream, &video_receive_stats]() { + video_receive_stats = video_stream->GetStats(); + }); + return DataSize::Bytes( + video_receive_stats.rtp_stats.packet_counter.TotalBytes()) / + s.TimeSinceStart(); +} + +TEST(GoogCcScenario, MaintainsLowRateInSafeResetTrial) { + const DataRate kLinkCapacity = DataRate::KilobitsPerSec(200); + const DataRate kStartRate = DataRate::KilobitsPerSec(300); + + ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled/"); + Scenario s("googcc_unit/safe_reset_low"); + auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kLinkCapacity; + c->delay = TimeDelta::Millis(10); + }); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = kStartRate; + }); + auto* route = s.CreateRoutes( + client, {send_net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow the controller to stabilize. + s.RunFor(TimeDelta::Millis(500)); + EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 50); + s.ChangeRoute(route->forward(), {send_net}); + // Allow new settings to propagate. + s.RunFor(TimeDelta::Millis(100)); + // Under the trial, the target should be unchanged for low rates. + EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 50); +} + +TEST(GoogCcScenario, CutsHighRateInSafeResetTrial) { + const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000); + const DataRate kStartRate = DataRate::KilobitsPerSec(300); + + ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled/"); + Scenario s("googcc_unit/safe_reset_high_cut"); + auto send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kLinkCapacity; + c->delay = TimeDelta::Millis(50); + }); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = kStartRate; + }); + auto* route = s.CreateRoutes( + client, {send_net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow the controller to stabilize. + s.RunFor(TimeDelta::Millis(500)); + EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 300); + s.ChangeRoute(route->forward(), {send_net}); + // Allow new settings to propagate. + s.RunFor(TimeDelta::Millis(50)); + // Under the trial, the target should be reset from high values. + EXPECT_NEAR(client->send_bandwidth().kbps(), kStartRate.kbps(), 30); +} + +TEST(GoogCcScenario, DetectsHighRateInSafeResetTrial) { + ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled,ack/"); + const DataRate kInitialLinkCapacity = DataRate::KilobitsPerSec(200); + const DataRate kNewLinkCapacity = DataRate::KilobitsPerSec(800); + const DataRate kStartRate = DataRate::KilobitsPerSec(300); + + Scenario s("googcc_unit/safe_reset_high_detect"); + auto* initial_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kInitialLinkCapacity; + c->delay = TimeDelta::Millis(50); + }); + auto* new_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kNewLinkCapacity; + c->delay = TimeDelta::Millis(50); + }); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = kStartRate; + }); + auto* route = s.CreateRoutes( + client, {initial_net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow the controller to stabilize. + s.RunFor(TimeDelta::Millis(2000)); + EXPECT_NEAR(client->send_bandwidth().kbps(), kInitialLinkCapacity.kbps(), 50); + s.ChangeRoute(route->forward(), {new_net}); + // Allow new settings to propagate, but not probes to be received. + s.RunFor(TimeDelta::Millis(50)); + // Under the field trial, the target rate should be unchanged since it's lower + // than the starting rate. + EXPECT_NEAR(client->send_bandwidth().kbps(), kInitialLinkCapacity.kbps(), 50); + // However, probing should have made us detect the higher rate. + // NOTE: This test causes high loss rate, and the loss-based estimator reduces + // the bitrate, making the test fail if we wait longer than one second here. + s.RunFor(TimeDelta::Millis(1000)); + EXPECT_GT(client->send_bandwidth().kbps(), kNewLinkCapacity.kbps() - 300); +} + +TEST(GoogCcScenario, TargetRateReducedOnPacingBufferBuildupInTrial) { + // Configure strict pacing to ensure build-up. + ScopedFieldTrials trial( + "WebRTC-CongestionWindow/QueueSize:100,MinBitrate:30000/" + "WebRTC-Video-Pacing/factor:1.0/" + "WebRTC-AddPacingToCongestionWindowPushback/Enabled/"); + + const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000); + const DataRate kStartRate = DataRate::KilobitsPerSec(1000); + + Scenario s("googcc_unit/pacing_buffer_buildup"); + auto* net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kLinkCapacity; + c->delay = TimeDelta::Millis(50); + }); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = kStartRate; + }); + auto* route = s.CreateRoutes( + client, {net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow some time for the buffer to build up. + s.RunFor(TimeDelta::Seconds(5)); + + // Without trial, pacer delay reaches ~250 ms. + EXPECT_LT(client->GetStats().pacer_delay_ms, 150); +} + +TEST(GoogCcScenario, NoBandwidthTogglingInLossControlTrial) { + ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/"); + Scenario s("googcc_unit/no_toggling"); + auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(2000); + c->loss_rate = 0.2; + c->delay = TimeDelta::Millis(10); + }); + + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = DataRate::KilobitsPerSec(300); + }); + auto* route = s.CreateRoutes( + client, {send_net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow the controller to initialize. + s.RunFor(TimeDelta::Millis(250)); + + std::queue bandwidth_history; + const TimeDelta step = TimeDelta::Millis(50); + for (TimeDelta time = TimeDelta::Zero(); time < TimeDelta::Millis(2000); + time += step) { + s.RunFor(step); + const TimeDelta window = TimeDelta::Millis(500); + if (bandwidth_history.size() >= window / step) + bandwidth_history.pop(); + bandwidth_history.push(client->send_bandwidth()); + EXPECT_LT( + CountBandwidthDips(bandwidth_history, DataRate::KilobitsPerSec(100)), + 2); + } +} + +TEST(GoogCcScenario, NoRttBackoffCollapseWhenVideoStops) { + ScopedFieldTrials trial("WebRTC-Bwe-MaxRttLimit/limit:2s/"); + Scenario s("googcc_unit/rttbackoff_video_stop"); + auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(2000); + c->delay = TimeDelta::Millis(100); + }); + + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000); + }); + auto* route = s.CreateRoutes( + client, {send_net}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + auto* video = s.CreateVideoStream(route->forward(), VideoStreamConfig()); + // Allow the controller to initialize, then stop video. + s.RunFor(TimeDelta::Seconds(1)); + video->send()->Stop(); + s.RunFor(TimeDelta::Seconds(4)); + EXPECT_GT(client->send_bandwidth().kbps(), 1000); +} + +TEST(GoogCcScenario, NoCrashOnVeryLateFeedback) { + Scenario s; + auto ret_net = s.CreateMutableSimulationNode(NetworkSimulationConfig()); + auto* route = s.CreateRoutes( + s.CreateClient("send", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}, + s.CreateClient("return", CallClientConfig()), {ret_net->node()}); + auto* video = s.CreateVideoStream(route->forward(), VideoStreamConfig()); + s.RunFor(TimeDelta::Seconds(5)); + // Delay feedback by several minutes. This will cause removal of the send time + // history for the packets as long as kSendTimeHistoryWindow is configured for + // a shorter time span. + ret_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(300)); + // Stopping video stream while waiting to save test execution time. + video->send()->Stop(); + s.RunFor(TimeDelta::Seconds(299)); + // Starting to cause addition of new packet to history, which cause old + // packets to be removed. + video->send()->Start(); + // Runs until the lost packets are received. We expect that this will run + // without causing any runtime failures. + s.RunFor(TimeDelta::Seconds(2)); +} + +TEST(GoogCcScenario, IsFairToTCP) { + Scenario s("googcc_unit/tcp_fairness"); + NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(1000); + net_conf.delay = TimeDelta::Millis(50); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000); + }); + auto send_net = {s.CreateSimulationNode(net_conf)}; + auto ret_net = {s.CreateSimulationNode(net_conf)}; + auto* route = s.CreateRoutes( + client, send_net, s.CreateClient("return", CallClientConfig()), ret_net); + s.CreateVideoStream(route->forward(), VideoStreamConfig()); + s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic( + s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net), + FakeTcpConfig())); + s.RunFor(TimeDelta::Seconds(10)); + + // Currently only testing for the upper limit as we in practice back out + // quite a lot in this scenario. If this behavior is fixed, we should add a + // lower bound to ensure it stays fixed. + EXPECT_LT(client->send_bandwidth().kbps(), 750); +} + +TEST(GoogCcScenario, FastRampupOnRembCapLifted) { + DataRate final_estimate = + RunRembDipScenario("googcc_unit/default_fast_rampup_on_remb_cap_lifted"); + EXPECT_GT(final_estimate.kbps(), 1500); +} + +TEST(GoogCcScenario, FallbackToLossBasedBweWithoutPacketFeedback) { + const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000); + const DataRate kStartRate = DataRate::KilobitsPerSec(1000); + + Scenario s("googcc_unit/high_loss_channel", false); + auto* net = s.CreateMutableSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = kLinkCapacity; + c->delay = TimeDelta::Millis(100); + }); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = kStartRate; + }); + auto* route = s.CreateRoutes( + client, {net->node()}, s.CreateClient("return", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + + // Create a config without packet feedback. + VideoStreamConfig video_config; + video_config.stream.packet_feedback = false; + s.CreateVideoStream(route->forward(), video_config); + + s.RunFor(TimeDelta::Seconds(20)); + // Bandwith does not backoff because network is normal. + EXPECT_GE(client->target_rate().kbps(), 500); + + // Update the network to create high loss ratio + net->UpdateConfig([](NetworkSimulationConfig* c) { c->loss_rate = 0.15; }); + s.RunFor(TimeDelta::Seconds(20)); + + // Bandwidth decreases thanks to loss based bwe v0. + EXPECT_LE(client->target_rate().kbps(), 300); +} + +class GoogCcRttTest : public ::testing::TestWithParam { + protected: + GoogCcFactoryConfig Config(bool feedback_only) { + GoogCcFactoryConfig config; + config.feedback_only = feedback_only; + return config; + } +}; + +TEST_P(GoogCcRttTest, CalculatesRttFromTransporFeedback) { + GoogCcFactoryConfig config(Config(/*feedback_only=*/GetParam())); + if (!GetParam()) { + // TODO(diepbp): understand the usage difference between + // UpdatePropagationRtt and UpdateRtt + GTEST_SKIP() << "This test should run only if " + "feedback_only is enabled"; + } + NetworkControllerTestFixture fixture(std::move(config)); + std::unique_ptr controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123); + TimeDelta one_way_delay = TimeDelta::Millis(10); + absl::optional rtt = absl::nullopt; + + TransportPacketsFeedback feedback = CreateTransportPacketsFeedback( + /*per_packet_network_delay=*/TimeDelta::Millis(50), one_way_delay, + /*send_time=*/current_time); + NetworkControlUpdate update = + controller->OnTransportPacketsFeedback(feedback); + current_time += TimeDelta::Millis(50); + update = controller->OnProcessInterval({.at_time = current_time}); + if (update.target_rate) { + rtt = update.target_rate->network_estimate.round_trip_time; + } + ASSERT_TRUE(rtt.has_value()); + EXPECT_EQ(rtt->ms(), 2 * one_way_delay.ms()); +} + +INSTANTIATE_TEST_SUITE_P(GoogCcRttTests, GoogCcRttTest, ::testing::Bool()); + +} // namespace test +} // namespace webrtc -- cgit v1.2.3