mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Add more accurate support for changing capacity in SimulatedNetwork
NetworkBehaviorInterface::RegisterDeliveryTimeChangedCallback adds support for a network behaviour to reschedule next time DequeueDeliverablePackets should be invoked. SimulatedNetwork::SetConfig(const BuiltInNetworkBehaviorConfig& config, Timestamp config_update_time) adds possibility to change the configuration at config_update_time. Delivery time of a packet currently in the narrow section, will depend on the link capacity before and after the update. Bug: webrtc:14525 Change-Id: I271251992d05c68f9160bb81811ea8f2efe9c921 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/349461 Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Commit-Queue: Per Kjellander <perkj@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42243}
This commit is contained in:
parent
2ee83c1784
commit
51a70c0d6f
10 changed files with 507 additions and 166 deletions
|
@ -849,7 +849,10 @@ rtc_source_set("simulated_network_api") {
|
||||||
"../rtc_base:macromagic",
|
"../rtc_base:macromagic",
|
||||||
"../rtc_base:random",
|
"../rtc_base:random",
|
||||||
]
|
]
|
||||||
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
|
absl_deps = [
|
||||||
|
"//third_party/abseil-cpp/absl/functional:any_invocable",
|
||||||
|
"//third_party/abseil-cpp/absl/types:optional",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO(srte): Move to network_emulation sub directory.
|
# TODO(srte): Move to network_emulation sub directory.
|
||||||
|
|
|
@ -14,10 +14,9 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <queue>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/functional/any_invocable.h"
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
#include "rtc_base/random.h"
|
#include "rtc_base/random.h"
|
||||||
#include "rtc_base/thread_annotations.h"
|
#include "rtc_base/thread_annotations.h"
|
||||||
|
@ -58,8 +57,8 @@ struct BuiltInNetworkBehaviorConfig {
|
||||||
int queue_delay_ms = 0;
|
int queue_delay_ms = 0;
|
||||||
// Standard deviation of the extra delay.
|
// Standard deviation of the extra delay.
|
||||||
int delay_standard_deviation_ms = 0;
|
int delay_standard_deviation_ms = 0;
|
||||||
// Link capacity in kbps.
|
// Link capacity in kbps. Negative number is treated as infinite capacity.
|
||||||
int link_capacity_kbps = 0;
|
int link_capacity_kbps = -1;
|
||||||
// Random packet loss, range 0 to 100.
|
// Random packet loss, range 0 to 100.
|
||||||
double loss_percent = 0.;
|
double loss_percent = 0.;
|
||||||
// If packets are allowed to be reordered.
|
// If packets are allowed to be reordered.
|
||||||
|
@ -115,6 +114,14 @@ class NetworkBehaviorInterface {
|
||||||
// random extra delay), in such case this method should be called again to get
|
// random extra delay), in such case this method should be called again to get
|
||||||
// the updated estimated delivery time.
|
// the updated estimated delivery time.
|
||||||
virtual absl::optional<int64_t> NextDeliveryTimeUs() const = 0;
|
virtual absl::optional<int64_t> NextDeliveryTimeUs() const = 0;
|
||||||
|
// Registers a callback that should be triggered by an implementation if the
|
||||||
|
// next NextDeliveryTimeUs() has changed between a call to NextDeliveryTimeUs
|
||||||
|
// and DequeueDeliverablePackets.
|
||||||
|
// The intended usage is to invoke NextDeliveryTimeUs and reschedule the
|
||||||
|
// DequeueDeliverablePackets call when network parameters (such as link
|
||||||
|
// capacity) changes.
|
||||||
|
virtual void RegisterDeliveryTimeChangedCallback(
|
||||||
|
absl::AnyInvocable<void()> callback) {}
|
||||||
virtual ~NetworkBehaviorInterface() = default;
|
virtual ~NetworkBehaviorInterface() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -95,23 +95,6 @@ rtc_library("emulated_network") {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
rtc_library("network_emulation_unittest") {
|
|
||||||
testonly = true
|
|
||||||
sources = [ "network_emulation_unittest.cc" ]
|
|
||||||
deps = [
|
|
||||||
":emulated_network",
|
|
||||||
"../:test_support",
|
|
||||||
"../..//test/network:simulated_network",
|
|
||||||
"../../api:simulated_network_api",
|
|
||||||
"../../api/units:time_delta",
|
|
||||||
"../../rtc_base:gunit_helpers",
|
|
||||||
"../../rtc_base:logging",
|
|
||||||
"../../rtc_base:rtc_event",
|
|
||||||
"../../rtc_base:task_queue_for_test",
|
|
||||||
"../../rtc_base/synchronization:mutex",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rtc_include_tests && !build_with_chromium) {
|
if (rtc_include_tests && !build_with_chromium) {
|
||||||
rtc_library("network_emulation_pc_unittest") {
|
rtc_library("network_emulation_pc_unittest") {
|
||||||
testonly = true
|
testonly = true
|
||||||
|
@ -163,6 +146,26 @@ rtc_library("cross_traffic_unittest") {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rtc_include_tests) {
|
if (rtc_include_tests) {
|
||||||
|
rtc_library("network_emulation_unittest") {
|
||||||
|
testonly = true
|
||||||
|
sources = [ "network_emulation_unittest.cc" ]
|
||||||
|
deps = [
|
||||||
|
":emulated_network",
|
||||||
|
"../:test_support",
|
||||||
|
"../..//test/network:simulated_network",
|
||||||
|
"../../api:create_time_controller",
|
||||||
|
"../../api:simulated_network_api",
|
||||||
|
"../../api/task_queue:task_queue",
|
||||||
|
"../../api/units:time_delta",
|
||||||
|
"../../api/units:timestamp",
|
||||||
|
"../../rtc_base:gunit_helpers",
|
||||||
|
"../../rtc_base:logging",
|
||||||
|
"../../rtc_base:rtc_event",
|
||||||
|
"../../rtc_base:task_queue_for_test",
|
||||||
|
"../../rtc_base/synchronization:mutex",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
rtc_library("feedback_generator") {
|
rtc_library("feedback_generator") {
|
||||||
testonly = true
|
testonly = true
|
||||||
sources = [
|
sources = [
|
||||||
|
@ -235,6 +238,7 @@ if (rtc_include_tests) {
|
||||||
"../../api/units:data_rate",
|
"../../api/units:data_rate",
|
||||||
"../../api/units:data_size",
|
"../../api/units:data_size",
|
||||||
"../../api/units:time_delta",
|
"../../api/units:time_delta",
|
||||||
|
"../../api/units:timestamp",
|
||||||
"//testing/gtest",
|
"//testing/gtest",
|
||||||
]
|
]
|
||||||
absl_deps = [ "//third_party/abseil-cpp/absl/algorithm:container" ]
|
absl_deps = [ "//third_party/abseil-cpp/absl/algorithm:container" ]
|
||||||
|
|
|
@ -311,6 +311,26 @@ EmulatedNetworkNodeStats EmulatedNetworkNodeStatsBuilder::Build() const {
|
||||||
return stats_;
|
return stats_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LinkEmulation::LinkEmulation(
|
||||||
|
Clock* clock,
|
||||||
|
absl::Nonnull<TaskQueueBase*> task_queue,
|
||||||
|
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
|
||||||
|
EmulatedNetworkReceiverInterface* receiver,
|
||||||
|
EmulatedNetworkStatsGatheringMode stats_gathering_mode)
|
||||||
|
: clock_(clock),
|
||||||
|
task_queue_(task_queue),
|
||||||
|
network_behavior_(std::move(network_behavior)),
|
||||||
|
receiver_(receiver),
|
||||||
|
stats_builder_(stats_gathering_mode) {
|
||||||
|
task_queue_->PostTask([&]() {
|
||||||
|
RTC_DCHECK_RUN_ON(task_queue_);
|
||||||
|
network_behavior_->RegisterDeliveryTimeChangedCallback([&]() {
|
||||||
|
RTC_DCHECK_RUN_ON(task_queue_);
|
||||||
|
UpdateProcessSchedule();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
|
void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
|
||||||
task_queue_->PostTask([this, packet = std::move(packet)]() mutable {
|
task_queue_->PostTask([this, packet = std::move(packet)]() mutable {
|
||||||
RTC_DCHECK_RUN_ON(task_queue_);
|
RTC_DCHECK_RUN_ON(task_queue_);
|
||||||
|
@ -326,28 +346,8 @@ void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
|
||||||
}
|
}
|
||||||
if (process_task_.Running())
|
if (process_task_.Running())
|
||||||
return;
|
return;
|
||||||
absl::optional<int64_t> next_time_us =
|
|
||||||
network_behavior_->NextDeliveryTimeUs();
|
UpdateProcessSchedule();
|
||||||
if (!next_time_us)
|
|
||||||
return;
|
|
||||||
Timestamp current_time = clock_->CurrentTime();
|
|
||||||
process_task_ = RepeatingTaskHandle::DelayedStart(
|
|
||||||
task_queue_,
|
|
||||||
std::max(TimeDelta::Zero(),
|
|
||||||
Timestamp::Micros(*next_time_us) - current_time),
|
|
||||||
[this]() {
|
|
||||||
RTC_DCHECK_RUN_ON(task_queue_);
|
|
||||||
Timestamp current_time = clock_->CurrentTime();
|
|
||||||
Process(current_time);
|
|
||||||
absl::optional<int64_t> next_time_us =
|
|
||||||
network_behavior_->NextDeliveryTimeUs();
|
|
||||||
if (!next_time_us) {
|
|
||||||
process_task_.Stop();
|
|
||||||
return TimeDelta::Zero(); // This is ignored.
|
|
||||||
}
|
|
||||||
RTC_DCHECK_GE(*next_time_us, current_time.us());
|
|
||||||
return Timestamp::Micros(*next_time_us) - current_time;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +385,35 @@ void LinkEmulation::Process(Timestamp at_time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LinkEmulation::UpdateProcessSchedule() {
|
||||||
|
RTC_DCHECK_RUN_ON(task_queue_);
|
||||||
|
if (process_task_.Running()) {
|
||||||
|
process_task_.Stop();
|
||||||
|
};
|
||||||
|
absl::optional<int64_t> next_time_us =
|
||||||
|
network_behavior_->NextDeliveryTimeUs();
|
||||||
|
if (!next_time_us)
|
||||||
|
return;
|
||||||
|
Timestamp current_time = clock_->CurrentTime();
|
||||||
|
process_task_ = RepeatingTaskHandle::DelayedStart(
|
||||||
|
task_queue_,
|
||||||
|
std::max(TimeDelta::Zero(),
|
||||||
|
Timestamp::Micros(*next_time_us) - current_time),
|
||||||
|
[this]() {
|
||||||
|
RTC_DCHECK_RUN_ON(task_queue_);
|
||||||
|
Timestamp current_time = clock_->CurrentTime();
|
||||||
|
Process(current_time);
|
||||||
|
absl::optional<int64_t> next_time_us =
|
||||||
|
network_behavior_->NextDeliveryTimeUs();
|
||||||
|
if (!next_time_us) {
|
||||||
|
process_task_.Stop();
|
||||||
|
return TimeDelta::Zero(); // This is ignored.
|
||||||
|
}
|
||||||
|
RTC_DCHECK_GE(*next_time_us, current_time.us());
|
||||||
|
return Timestamp::Micros(*next_time_us) - current_time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRouterNode::NetworkRouterNode(absl::Nonnull<TaskQueueBase*> task_queue)
|
NetworkRouterNode::NetworkRouterNode(absl::Nonnull<TaskQueueBase*> task_queue)
|
||||||
: task_queue_(task_queue) {}
|
: task_queue_(task_queue) {}
|
||||||
|
|
||||||
|
|
|
@ -150,12 +150,7 @@ class LinkEmulation : public EmulatedNetworkReceiverInterface {
|
||||||
absl::Nonnull<TaskQueueBase*> task_queue,
|
absl::Nonnull<TaskQueueBase*> task_queue,
|
||||||
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
|
std::unique_ptr<NetworkBehaviorInterface> network_behavior,
|
||||||
EmulatedNetworkReceiverInterface* receiver,
|
EmulatedNetworkReceiverInterface* receiver,
|
||||||
EmulatedNetworkStatsGatheringMode stats_gathering_mode)
|
EmulatedNetworkStatsGatheringMode stats_gathering_mode);
|
||||||
: clock_(clock),
|
|
||||||
task_queue_(task_queue),
|
|
||||||
network_behavior_(std::move(network_behavior)),
|
|
||||||
receiver_(receiver),
|
|
||||||
stats_builder_(stats_gathering_mode) {}
|
|
||||||
void OnPacketReceived(EmulatedIpPacket packet) override;
|
void OnPacketReceived(EmulatedIpPacket packet) override;
|
||||||
|
|
||||||
EmulatedNetworkNodeStats stats() const;
|
EmulatedNetworkNodeStats stats() const;
|
||||||
|
@ -167,6 +162,7 @@ class LinkEmulation : public EmulatedNetworkReceiverInterface {
|
||||||
EmulatedIpPacket packet;
|
EmulatedIpPacket packet;
|
||||||
bool removed;
|
bool removed;
|
||||||
};
|
};
|
||||||
|
void UpdateProcessSchedule() RTC_RUN_ON(task_queue_);
|
||||||
void Process(Timestamp at_time) RTC_RUN_ON(task_queue_);
|
void Process(Timestamp at_time) RTC_RUN_ON(task_queue_);
|
||||||
|
|
||||||
Clock* const clock_;
|
Clock* const clock_;
|
||||||
|
|
|
@ -14,8 +14,11 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
#include "api/task_queue/task_queue_base.h"
|
||||||
|
#include "api/test/create_time_controller.h"
|
||||||
#include "api/test/simulated_network.h"
|
#include "api/test/simulated_network.h"
|
||||||
#include "api/units/time_delta.h"
|
#include "api/units/time_delta.h"
|
||||||
|
#include "api/units/timestamp.h"
|
||||||
#include "rtc_base/event.h"
|
#include "rtc_base/event.h"
|
||||||
#include "rtc_base/gunit.h"
|
#include "rtc_base/gunit.h"
|
||||||
#include "rtc_base/synchronization/mutex.h"
|
#include "rtc_base/synchronization/mutex.h"
|
||||||
|
@ -76,6 +79,23 @@ class MockReceiver : public EmulatedNetworkReceiverInterface {
|
||||||
MOCK_METHOD(void, OnPacketReceived, (EmulatedIpPacket packet), (override));
|
MOCK_METHOD(void, OnPacketReceived, (EmulatedIpPacket packet), (override));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MockNetworkBehaviourInterface : public NetworkBehaviorInterface {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(bool, EnqueuePacket, (PacketInFlightInfo), (override));
|
||||||
|
MOCK_METHOD(std::vector<PacketDeliveryInfo>,
|
||||||
|
DequeueDeliverablePackets,
|
||||||
|
(int64_t),
|
||||||
|
(override));
|
||||||
|
MOCK_METHOD(absl::optional<int64_t>,
|
||||||
|
NextDeliveryTimeUs,
|
||||||
|
(),
|
||||||
|
(const override));
|
||||||
|
MOCK_METHOD(void,
|
||||||
|
RegisterDeliveryTimeChangedCallback,
|
||||||
|
(absl::AnyInvocable<void()>),
|
||||||
|
(override));
|
||||||
|
};
|
||||||
|
|
||||||
class NetworkEmulationManagerThreeNodesRoutingTest : public ::testing::Test {
|
class NetworkEmulationManagerThreeNodesRoutingTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
NetworkEmulationManagerThreeNodesRoutingTest() {
|
NetworkEmulationManagerThreeNodesRoutingTest() {
|
||||||
|
@ -672,5 +692,45 @@ TEST(NetworkEmulationManagerTURNTest, ClientTraffic) {
|
||||||
emulation.time_controller()->AdvanceTime(TimeDelta::Seconds(1));
|
emulation.time_controller()->AdvanceTime(TimeDelta::Seconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(LinkEmulationTest, HandlesDeliveryTimeChangedCallback) {
|
||||||
|
constexpr uint32_t kEndpointIp = 0xC0A80011; // 192.168.0.17
|
||||||
|
NetworkEmulationManagerImpl network_manager(
|
||||||
|
TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault);
|
||||||
|
auto mock_behaviour =
|
||||||
|
std::make_unique<::testing::NiceMock<MockNetworkBehaviourInterface>>();
|
||||||
|
MockNetworkBehaviourInterface* mock_behaviour_ptr = mock_behaviour.get();
|
||||||
|
absl::AnyInvocable<void()> delivery_time_changed_callback = nullptr;
|
||||||
|
TaskQueueBase* emulation_task_queue = nullptr;
|
||||||
|
EXPECT_CALL(*mock_behaviour_ptr, RegisterDeliveryTimeChangedCallback)
|
||||||
|
.WillOnce([&](absl::AnyInvocable<void()> callback) {
|
||||||
|
delivery_time_changed_callback = std::move(callback);
|
||||||
|
emulation_task_queue = TaskQueueBase::Current();
|
||||||
|
});
|
||||||
|
LinkEmulation* link =
|
||||||
|
network_manager.CreateEmulatedNode(std::move(mock_behaviour))->link();
|
||||||
|
network_manager.time_controller()->AdvanceTime(TimeDelta::Zero());
|
||||||
|
ASSERT_TRUE(delivery_time_changed_callback);
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_behaviour_ptr, EnqueuePacket);
|
||||||
|
EXPECT_CALL(*mock_behaviour_ptr, NextDeliveryTimeUs)
|
||||||
|
.WillOnce(::testing::Return(
|
||||||
|
network_manager.time_controller()->GetClock()->TimeInMicroseconds() +
|
||||||
|
10));
|
||||||
|
link->OnPacketReceived(EmulatedIpPacket(
|
||||||
|
rtc::SocketAddress(kEndpointIp, 50), rtc::SocketAddress(kEndpointIp, 79),
|
||||||
|
rtc::CopyOnWriteBuffer(10), Timestamp::Millis(1)));
|
||||||
|
network_manager.time_controller()->AdvanceTime(TimeDelta::Zero());
|
||||||
|
|
||||||
|
// Test that NetworkBehaviour can reschedule time for delivery. When
|
||||||
|
// delivery_time_changed_callback is triggered, LinkEmulation re-query the
|
||||||
|
// next delivery time.
|
||||||
|
EXPECT_CALL(*mock_behaviour_ptr, NextDeliveryTimeUs)
|
||||||
|
.WillOnce(::testing::Return(
|
||||||
|
network_manager.time_controller()->GetClock()->TimeInMicroseconds() +
|
||||||
|
20));
|
||||||
|
emulation_task_queue->PostTask([&]() { delivery_time_changed_callback(); });
|
||||||
|
network_manager.time_controller()->AdvanceTime(TimeDelta::Zero());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -15,40 +15,45 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/types/optional.h"
|
||||||
|
#include "api/test/simulated_network.h"
|
||||||
#include "api/units/data_rate.h"
|
#include "api/units/data_rate.h"
|
||||||
#include "api/units/data_size.h"
|
#include "api/units/data_size.h"
|
||||||
#include "api/units/time_delta.h"
|
#include "api/units/time_delta.h"
|
||||||
|
#include "api/units/timestamp.h"
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Calculate the time (in microseconds) that takes to send N `bits` on a
|
// Calculate the time that it takes to send N `bits` on a
|
||||||
// network with link capacity equal to `capacity_kbps` starting at time
|
// network with link capacity equal to `capacity_kbps` starting at time
|
||||||
// `start_time_us`.
|
// `start_time`.
|
||||||
int64_t CalculateArrivalTimeUs(int64_t start_time_us,
|
Timestamp CalculateArrivalTime(Timestamp start_time,
|
||||||
int64_t bits,
|
int64_t bits,
|
||||||
int capacity_kbps) {
|
int capacity_kbps) {
|
||||||
// If capacity is 0, the link capacity is assumed to be infinite.
|
// If capacity is negative, the link capacity is assumed to be infinite.
|
||||||
if (capacity_kbps == 0) {
|
if (capacity_kbps < 0) {
|
||||||
return start_time_us;
|
return start_time;
|
||||||
}
|
}
|
||||||
|
if (capacity_kbps == 0) {
|
||||||
|
return Timestamp::PlusInfinity();
|
||||||
|
}
|
||||||
|
|
||||||
// Adding `capacity - 1` to the numerator rounds the extra delay caused by
|
// Adding `capacity - 1` to the numerator rounds the extra delay caused by
|
||||||
// capacity constraints up to an integral microsecond. Sending 0 bits takes 0
|
// capacity constraints up to an integral microsecond. Sending 0 bits takes 0
|
||||||
// extra time, while sending 1 bit gets rounded up to 1 (the multiplication by
|
// extra time, while sending 1 bit gets rounded up to 1 (the multiplication by
|
||||||
// 1000 is because capacity is in kbps).
|
// 1000 is because capacity is in kbps).
|
||||||
// The factor 1000 comes from 10^6 / 10^3, where 10^6 is due to the time unit
|
// The factor 1000 comes from 10^6 / 10^3, where 10^6 is due to the time unit
|
||||||
// being us and 10^3 is due to the rate unit being kbps.
|
// being us and 10^3 is due to the rate unit being kbps.
|
||||||
return start_time_us + ((1000 * bits + capacity_kbps - 1) / capacity_kbps);
|
return start_time +
|
||||||
|
TimeDelta::Micros((1000 * bits + capacity_kbps - 1) / capacity_kbps);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SimulatedNetwork::SimulatedNetwork(Config config, uint64_t random_seed)
|
SimulatedNetwork::SimulatedNetwork(Config config, uint64_t random_seed)
|
||||||
: random_(random_seed),
|
: random_(random_seed), bursting_(false), last_enqueue_time_us_(0) {
|
||||||
bursting_(false),
|
|
||||||
last_enqueue_time_us_(0),
|
|
||||||
last_capacity_link_exit_time_(0) {
|
|
||||||
SetConfig(config);
|
SetConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +84,30 @@ void SimulatedNetwork::SetConfig(const Config& config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimulatedNetwork::SetConfig(const BuiltInNetworkBehaviorConfig& new_config,
|
||||||
|
Timestamp config_update_time) {
|
||||||
|
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
||||||
|
|
||||||
|
if (!capacity_link_.empty()) {
|
||||||
|
// Calculate and update how large portion of the packet first in the
|
||||||
|
// capacity link is left to to send at time `config_update_time`.
|
||||||
|
const BuiltInNetworkBehaviorConfig& current_config =
|
||||||
|
GetConfigState().config;
|
||||||
|
TimeDelta duration_with_current_config =
|
||||||
|
config_update_time - capacity_link_.front().last_update_time;
|
||||||
|
RTC_DCHECK_GE(duration_with_current_config, TimeDelta::Zero());
|
||||||
|
capacity_link_.front().bits_left_to_send -= std::min(
|
||||||
|
duration_with_current_config.ms() * current_config.link_capacity_kbps,
|
||||||
|
capacity_link_.front().bits_left_to_send);
|
||||||
|
capacity_link_.front().last_update_time = config_update_time;
|
||||||
|
}
|
||||||
|
SetConfig(new_config);
|
||||||
|
UpdateCapacityQueue(GetConfigState(), config_update_time);
|
||||||
|
if (UpdateNextProcessTime() && next_process_time_changed_callback_) {
|
||||||
|
next_process_time_changed_callback_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SimulatedNetwork::UpdateConfig(
|
void SimulatedNetwork::UpdateConfig(
|
||||||
std::function<void(BuiltInNetworkBehaviorConfig*)> config_modifier) {
|
std::function<void(BuiltInNetworkBehaviorConfig*)> config_modifier) {
|
||||||
MutexLock lock(&config_lock_);
|
MutexLock lock(&config_lock_);
|
||||||
|
@ -117,24 +146,31 @@ bool SimulatedNetwork::EnqueuePacket(PacketInFlightInfo packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the packet has been sent before the previous packet in the network left
|
// Note that arrival time will be updated when previous packets are dequeued
|
||||||
// the capacity queue, let's ensure the new packet will start its trip in the
|
// from the capacity link.
|
||||||
// network after the last bit of the previous packet has left it.
|
// A packet can not enter the narrow section before the last packet has exit.
|
||||||
int64_t packet_send_time_us = packet.send_time_us;
|
Timestamp enqueue_time = Timestamp::Micros(packet.send_time_us);
|
||||||
if (!capacity_link_.empty()) {
|
Timestamp arrival_time =
|
||||||
packet_send_time_us =
|
capacity_link_.empty()
|
||||||
std::max(packet_send_time_us, capacity_link_.back().arrival_time_us);
|
? CalculateArrivalTime(
|
||||||
}
|
std::max(enqueue_time, last_capacity_link_exit_time_),
|
||||||
capacity_link_.push({.packet = packet,
|
packet.size * 8, state.config.link_capacity_kbps)
|
||||||
.arrival_time_us = CalculateArrivalTimeUs(
|
: Timestamp::PlusInfinity();
|
||||||
packet_send_time_us, packet.size * 8,
|
capacity_link_.push(
|
||||||
state.config.link_capacity_kbps)});
|
{.packet = packet,
|
||||||
|
.last_update_time = enqueue_time,
|
||||||
|
.bits_left_to_send = 8 * static_cast<int64_t>(packet.size),
|
||||||
|
.arrival_time = arrival_time});
|
||||||
|
|
||||||
// Only update `next_process_time_us_` if not already set (if set, there is no
|
// Only update `next_process_time_` if not already set. Otherwise,
|
||||||
// way that a new packet will make the `next_process_time_us_` change).
|
// next_process_time_ is calculated when a packet is dequeued. Note that this
|
||||||
if (!next_process_time_us_) {
|
// means that the newly enqueud packet risk having an arrival time before
|
||||||
|
// `next_process_time_` if packet reordering is allowed and
|
||||||
|
// config.delay_standard_deviation_ms is set.
|
||||||
|
// TODO(bugs.webrtc.org/14525): Consider preventing this.
|
||||||
|
if (next_process_time_.IsInfinite()) {
|
||||||
RTC_DCHECK_EQ(capacity_link_.size(), 1);
|
RTC_DCHECK_EQ(capacity_link_.size(), 1);
|
||||||
next_process_time_us_ = capacity_link_.front().arrival_time_us;
|
next_process_time_ = arrival_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_enqueue_time_us_ = packet.send_time_us;
|
last_enqueue_time_us_ = packet.send_time_us;
|
||||||
|
@ -143,74 +179,82 @@ bool SimulatedNetwork::EnqueuePacket(PacketInFlightInfo packet) {
|
||||||
|
|
||||||
absl::optional<int64_t> SimulatedNetwork::NextDeliveryTimeUs() const {
|
absl::optional<int64_t> SimulatedNetwork::NextDeliveryTimeUs() const {
|
||||||
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
||||||
return next_process_time_us_;
|
if (next_process_time_.IsFinite()) {
|
||||||
|
return next_process_time_.us();
|
||||||
|
}
|
||||||
|
return absl::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimulatedNetwork::UpdateCapacityQueue(ConfigState state,
|
void SimulatedNetwork::UpdateCapacityQueue(ConfigState state,
|
||||||
int64_t time_now_us) {
|
Timestamp time_now) {
|
||||||
// If there is at least one packet in the `capacity_link_`, let's update its
|
// Only the first packet in capacity_link_ have a calculated arrival time
|
||||||
// arrival time to take into account changes in the network configuration
|
// (when packet leave the narrow section), and time when it entered the narrow
|
||||||
// since the last call to UpdateCapacityQueue.
|
// section. Also, the configuration may have changed. Thus we need to
|
||||||
|
// calculate the arrival time again before maybe moving the packet to the
|
||||||
|
// delay link.
|
||||||
if (!capacity_link_.empty()) {
|
if (!capacity_link_.empty()) {
|
||||||
capacity_link_.front().arrival_time_us = CalculateArrivalTimeUs(
|
capacity_link_.front().last_update_time = std::max(
|
||||||
std::max(capacity_link_.front().packet.send_time_us,
|
capacity_link_.front().last_update_time, last_capacity_link_exit_time_);
|
||||||
last_capacity_link_exit_time_),
|
capacity_link_.front().arrival_time =
|
||||||
capacity_link_.front().packet.size * 8,
|
CalculateArrivalTime(capacity_link_.front().last_update_time,
|
||||||
state.config.link_capacity_kbps);
|
capacity_link_.front().bits_left_to_send,
|
||||||
|
state.config.link_capacity_kbps);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The capacity link is empty or the first packet is not expected to exit yet.
|
// The capacity link is empty or the first packet is not expected to exit yet.
|
||||||
if (capacity_link_.empty() ||
|
if (capacity_link_.empty() ||
|
||||||
time_now_us < capacity_link_.front().arrival_time_us) {
|
time_now < capacity_link_.front().arrival_time) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool reorder_packets = false;
|
bool reorder_packets = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Time to get this packet (the original or just updated arrival_time_us is
|
// Time to get this packet (the original or just updated arrival_time is
|
||||||
// smaller or equal to time_now_us).
|
// smaller or equal to time_now_us).
|
||||||
PacketInfo packet = capacity_link_.front();
|
PacketInfo packet = capacity_link_.front();
|
||||||
|
RTC_DCHECK(packet.arrival_time.IsFinite());
|
||||||
capacity_link_.pop();
|
capacity_link_.pop();
|
||||||
|
|
||||||
// If the network is paused, the pause will be implemented as an extra delay
|
// If the network is paused, the pause will be implemented as an extra delay
|
||||||
// to be spent in the `delay_link_` queue.
|
// to be spent in the `delay_link_` queue.
|
||||||
if (state.pause_transmission_until_us > packet.arrival_time_us) {
|
if (state.pause_transmission_until_us > packet.arrival_time.us()) {
|
||||||
packet.arrival_time_us = state.pause_transmission_until_us;
|
packet.arrival_time =
|
||||||
|
Timestamp::Micros(state.pause_transmission_until_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the original arrival time, before applying packet loss or extra
|
// Store the original arrival time, before applying packet loss or extra
|
||||||
// delay. This is needed to know when it is the first available time the
|
// delay. This is needed to know when it is the first available time the
|
||||||
// next packet in the `capacity_link_` queue can start transmitting.
|
// next packet in the `capacity_link_` queue can start transmitting.
|
||||||
last_capacity_link_exit_time_ = packet.arrival_time_us;
|
last_capacity_link_exit_time_ = packet.arrival_time;
|
||||||
|
|
||||||
// Drop packets at an average rate of `state.config.loss_percent` with
|
// Drop packets at an average rate of `state.config.loss_percent` with
|
||||||
// and average loss burst length of `state.config.avg_burst_loss_length`.
|
// and average loss burst length of `state.config.avg_burst_loss_length`.
|
||||||
if ((bursting_ && random_.Rand<double>() < state.prob_loss_bursting) ||
|
if ((bursting_ && random_.Rand<double>() < state.prob_loss_bursting) ||
|
||||||
(!bursting_ && random_.Rand<double>() < state.prob_start_bursting)) {
|
(!bursting_ && random_.Rand<double>() < state.prob_start_bursting)) {
|
||||||
bursting_ = true;
|
bursting_ = true;
|
||||||
packet.arrival_time_us = PacketDeliveryInfo::kNotReceived;
|
packet.arrival_time = Timestamp::MinusInfinity();
|
||||||
} else {
|
} else {
|
||||||
// If packets are not dropped, apply extra delay as configured.
|
// If packets are not dropped, apply extra delay as configured.
|
||||||
bursting_ = false;
|
bursting_ = false;
|
||||||
int64_t arrival_time_jitter_us = std::max(
|
TimeDelta arrival_time_jitter = TimeDelta::Micros(std::max(
|
||||||
random_.Gaussian(state.config.queue_delay_ms * 1000,
|
random_.Gaussian(state.config.queue_delay_ms * 1000,
|
||||||
state.config.delay_standard_deviation_ms * 1000),
|
state.config.delay_standard_deviation_ms * 1000),
|
||||||
0.0);
|
0.0));
|
||||||
|
|
||||||
// If reordering is not allowed then adjust arrival_time_jitter
|
// If reordering is not allowed then adjust arrival_time_jitter
|
||||||
// to make sure all packets are sent in order.
|
// to make sure all packets are sent in order.
|
||||||
int64_t last_arrival_time_us =
|
Timestamp last_arrival_time = delay_link_.empty()
|
||||||
delay_link_.empty() ? -1 : delay_link_.back().arrival_time_us;
|
? Timestamp::MinusInfinity()
|
||||||
|
: delay_link_.back().arrival_time;
|
||||||
if (!state.config.allow_reordering && !delay_link_.empty() &&
|
if (!state.config.allow_reordering && !delay_link_.empty() &&
|
||||||
packet.arrival_time_us + arrival_time_jitter_us <
|
packet.arrival_time + arrival_time_jitter < last_arrival_time) {
|
||||||
last_arrival_time_us) {
|
arrival_time_jitter = last_arrival_time - packet.arrival_time;
|
||||||
arrival_time_jitter_us = last_arrival_time_us - packet.arrival_time_us;
|
|
||||||
}
|
}
|
||||||
packet.arrival_time_us += arrival_time_jitter_us;
|
packet.arrival_time += arrival_time_jitter;
|
||||||
|
|
||||||
// Optimization: Schedule a reorder only when a packet will exit before
|
// Optimization: Schedule a reorder only when a packet will exit before
|
||||||
// the one in front.
|
// the one in front.
|
||||||
if (last_arrival_time_us > packet.arrival_time_us) {
|
if (last_arrival_time > packet.arrival_time) {
|
||||||
reorder_packets = true;
|
reorder_packets = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,23 +265,23 @@ void SimulatedNetwork::UpdateCapacityQueue(ConfigState state,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If instead there is another packet in the `capacity_link_` queue, let's
|
// If instead there is another packet in the `capacity_link_` queue, let's
|
||||||
// calculate its arrival_time_us based on the latest config (which might
|
// calculate its arrival_time based on the latest config (which might
|
||||||
// have been changed since it was enqueued).
|
// have been changed since it was enqueued).
|
||||||
int64_t next_start = std::max(last_capacity_link_exit_time_,
|
Timestamp next_start = std::max(last_capacity_link_exit_time_,
|
||||||
capacity_link_.front().packet.send_time_us);
|
capacity_link_.front().last_update_time);
|
||||||
capacity_link_.front().arrival_time_us = CalculateArrivalTimeUs(
|
capacity_link_.front().arrival_time =
|
||||||
next_start, capacity_link_.front().packet.size * 8,
|
CalculateArrivalTime(next_start, capacity_link_.front().packet.size * 8,
|
||||||
state.config.link_capacity_kbps);
|
state.config.link_capacity_kbps);
|
||||||
// And if the next packet in the queue needs to exit, let's dequeue it.
|
// And if the next packet in the queue needs to exit, let's dequeue it.
|
||||||
} while (capacity_link_.front().arrival_time_us <= time_now_us);
|
} while (capacity_link_.front().arrival_time <= time_now);
|
||||||
|
|
||||||
if (state.config.allow_reordering && reorder_packets) {
|
if (state.config.allow_reordering && reorder_packets) {
|
||||||
// Packets arrived out of order and since the network config allows
|
// Packets arrived out of order and since the network config allows
|
||||||
// reordering, let's sort them per arrival_time_us to make so they will also
|
// reordering, let's sort them per arrival_time to make so they will also
|
||||||
// be delivered out of order.
|
// be delivered out of order.
|
||||||
std::stable_sort(delay_link_.begin(), delay_link_.end(),
|
std::stable_sort(delay_link_.begin(), delay_link_.end(),
|
||||||
[](const PacketInfo& p1, const PacketInfo& p2) {
|
[](const PacketInfo& p1, const PacketInfo& p2) {
|
||||||
return p1.arrival_time_us < p2.arrival_time_us;
|
return p1.arrival_time < p2.arrival_time;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,27 +294,49 @@ SimulatedNetwork::ConfigState SimulatedNetwork::GetConfigState() const {
|
||||||
std::vector<PacketDeliveryInfo> SimulatedNetwork::DequeueDeliverablePackets(
|
std::vector<PacketDeliveryInfo> SimulatedNetwork::DequeueDeliverablePackets(
|
||||||
int64_t receive_time_us) {
|
int64_t receive_time_us) {
|
||||||
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
||||||
|
Timestamp receive_time = Timestamp::Micros(receive_time_us);
|
||||||
|
|
||||||
UpdateCapacityQueue(GetConfigState(), receive_time_us);
|
UpdateCapacityQueue(GetConfigState(), receive_time);
|
||||||
std::vector<PacketDeliveryInfo> packets_to_deliver;
|
std::vector<PacketDeliveryInfo> packets_to_deliver;
|
||||||
|
|
||||||
// Check the extra delay queue.
|
// Check the extra delay queue.
|
||||||
while (!delay_link_.empty() &&
|
while (!delay_link_.empty() &&
|
||||||
receive_time_us >= delay_link_.front().arrival_time_us) {
|
receive_time >= delay_link_.front().arrival_time) {
|
||||||
PacketInfo packet_info = delay_link_.front();
|
PacketInfo packet_info = delay_link_.front();
|
||||||
packets_to_deliver.emplace_back(
|
packets_to_deliver.emplace_back(PacketDeliveryInfo(
|
||||||
PacketDeliveryInfo(packet_info.packet, packet_info.arrival_time_us));
|
packet_info.packet, packet_info.arrival_time.IsFinite()
|
||||||
|
? packet_info.arrival_time.us()
|
||||||
|
: PacketDeliveryInfo::kNotReceived));
|
||||||
delay_link_.pop_front();
|
delay_link_.pop_front();
|
||||||
}
|
}
|
||||||
|
// There is no need to invoke `next_process_time_changed_callback_` here since
|
||||||
if (!delay_link_.empty()) {
|
// it is expected that the user of NetworkBehaviorInterface calls
|
||||||
next_process_time_us_ = delay_link_.front().arrival_time_us;
|
// NextDeliveryTimeUs after DequeueDeliverablePackets. See
|
||||||
} else if (!capacity_link_.empty()) {
|
// NetworkBehaviorInterface.
|
||||||
next_process_time_us_ = capacity_link_.front().arrival_time_us;
|
UpdateNextProcessTime();
|
||||||
} else {
|
|
||||||
next_process_time_us_.reset();
|
|
||||||
}
|
|
||||||
return packets_to_deliver;
|
return packets_to_deliver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SimulatedNetwork::UpdateNextProcessTime() {
|
||||||
|
Timestamp next_process_time = next_process_time_;
|
||||||
|
|
||||||
|
next_process_time_ = Timestamp::PlusInfinity();
|
||||||
|
for (const PacketInfo& packet : delay_link_) {
|
||||||
|
if (packet.arrival_time.IsFinite()) {
|
||||||
|
next_process_time_ = packet.arrival_time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next_process_time_.IsInfinite() && !capacity_link_.empty()) {
|
||||||
|
next_process_time_ = capacity_link_.front().arrival_time;
|
||||||
|
}
|
||||||
|
return next_process_time != next_process_time_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedNetwork::RegisterDeliveryTimeChangedCallback(
|
||||||
|
absl::AnyInvocable<void()> callback) {
|
||||||
|
RTC_DCHECK_RUNS_SERIALIZED(&process_checker_);
|
||||||
|
next_process_time_changed_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -19,7 +20,6 @@
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
#include "api/sequence_checker.h"
|
#include "api/sequence_checker.h"
|
||||||
#include "api/test/simulated_network.h"
|
#include "api/test/simulated_network.h"
|
||||||
#include "api/units/data_size.h"
|
|
||||||
#include "api/units/timestamp.h"
|
#include "api/units/timestamp.h"
|
||||||
#include "rtc_base/race_checker.h"
|
#include "rtc_base/race_checker.h"
|
||||||
#include "rtc_base/random.h"
|
#include "rtc_base/random.h"
|
||||||
|
@ -32,7 +32,8 @@ namespace webrtc {
|
||||||
//
|
//
|
||||||
// This is a basic implementation of NetworkBehaviorInterface that supports:
|
// This is a basic implementation of NetworkBehaviorInterface that supports:
|
||||||
// - Packet loss
|
// - Packet loss
|
||||||
// - Capacity delay
|
// - Capacity delay: Delay caused by a narrow section that only allows one
|
||||||
|
// packet through at the time with a limited capacity.
|
||||||
// - Extra delay with or without packets reorder
|
// - Extra delay with or without packets reorder
|
||||||
// - Packet overhead
|
// - Packet overhead
|
||||||
// - Queue max capacity
|
// - Queue max capacity
|
||||||
|
@ -46,10 +47,20 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
// EnqueuePacket but also packets in the network that have not left the
|
// EnqueuePacket but also packets in the network that have not left the
|
||||||
// network emulation. Packets that are ready to be retrieved by
|
// network emulation. Packets that are ready to be retrieved by
|
||||||
// DequeueDeliverablePackets are not affected by the new configuration.
|
// DequeueDeliverablePackets are not affected by the new configuration.
|
||||||
// TODO(bugs.webrtc.org/14525): Fix SetConfig and make it apply only to the
|
// This method can be invoked directly by tests on any thread/sequence, but is
|
||||||
// part of the packet that is currently being sent (instead of applying to
|
// less accurate than the version with timestamp since changes to the
|
||||||
// all of it).
|
// configuration does not take affect until the time returned by
|
||||||
|
// NextDeliveryTimeUs has passed.
|
||||||
void SetConfig(const Config& config) override;
|
void SetConfig(const Config& config) override;
|
||||||
|
// Updates the configuration at a specific time.
|
||||||
|
// Note that packets that have already passed the narrow section constrained
|
||||||
|
// by link capacity will not be affected by the change. If packet re-ordering
|
||||||
|
// is not allowed, packets with new shorter queue delays will arrive
|
||||||
|
// immediately after packets with the old, longer queue delays. Must be
|
||||||
|
// invoked on the same sequence as other methods in NetworkBehaviorInterface.
|
||||||
|
void SetConfig(const BuiltInNetworkBehaviorConfig& config,
|
||||||
|
Timestamp config_update_time);
|
||||||
|
|
||||||
void UpdateConfig(std::function<void(BuiltInNetworkBehaviorConfig*)>
|
void UpdateConfig(std::function<void(BuiltInNetworkBehaviorConfig*)>
|
||||||
config_modifier) override;
|
config_modifier) override;
|
||||||
void PauseTransmissionUntil(int64_t until_us) override;
|
void PauseTransmissionUntil(int64_t until_us) override;
|
||||||
|
@ -60,12 +71,20 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
int64_t receive_time_us) override;
|
int64_t receive_time_us) override;
|
||||||
|
|
||||||
absl::optional<int64_t> NextDeliveryTimeUs() const override;
|
absl::optional<int64_t> NextDeliveryTimeUs() const override;
|
||||||
|
void RegisterDeliveryTimeChangedCallback(
|
||||||
|
absl::AnyInvocable<void()> callback) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PacketInfo {
|
struct PacketInfo {
|
||||||
PacketInFlightInfo packet;
|
PacketInFlightInfo packet;
|
||||||
|
// Time the packet was last updated by the capacity link.
|
||||||
|
Timestamp last_update_time;
|
||||||
|
// Size of the packet left to send through the capacity link. May differ
|
||||||
|
// from the packet size if the link capacity changes while the packet is in
|
||||||
|
// the capacity link.
|
||||||
|
int64_t bits_left_to_send;
|
||||||
// Time when the packet has left (or will leave) the network.
|
// Time when the packet has left (or will leave) the network.
|
||||||
int64_t arrival_time_us;
|
Timestamp arrival_time;
|
||||||
};
|
};
|
||||||
// Contains current configuration state.
|
// Contains current configuration state.
|
||||||
struct ConfigState {
|
struct ConfigState {
|
||||||
|
@ -80,8 +99,12 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
int64_t pause_transmission_until_us = 0;
|
int64_t pause_transmission_until_us = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Calculates next_process_time_. Returns true if changed.
|
||||||
|
bool UpdateNextProcessTime() RTC_RUN_ON(&process_checker_);
|
||||||
// Moves packets from capacity- to delay link.
|
// Moves packets from capacity- to delay link.
|
||||||
void UpdateCapacityQueue(ConfigState state, int64_t time_now_us)
|
// If `previouse_config` is set, it is the config that was used until
|
||||||
|
// `time_now_us`
|
||||||
|
void UpdateCapacityQueue(ConfigState state, Timestamp time_now)
|
||||||
RTC_RUN_ON(&process_checker_);
|
RTC_RUN_ON(&process_checker_);
|
||||||
ConfigState GetConfigState() const;
|
ConfigState GetConfigState() const;
|
||||||
|
|
||||||
|
@ -96,11 +119,11 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
// only be able to deliver 1000 bits per second).
|
// only be able to deliver 1000 bits per second).
|
||||||
//
|
//
|
||||||
// Invariant:
|
// Invariant:
|
||||||
// The head of the `capacity_link_` has arrival_time_us correctly set to the
|
// The head of the `capacity_link_` has arrival_time correctly set to the
|
||||||
// time when the packet is supposed to be delivered (without accounting
|
// time when the packet is supposed to be delivered (without accounting
|
||||||
// potential packet loss or potential extra delay and without accounting for a
|
// potential packet loss or potential extra delay and without accounting for a
|
||||||
// new configuration of the network, which requires a re-computation of the
|
// new configuration of the network, which requires a re-computation of the
|
||||||
// arrival_time_us).
|
// arrival_time).
|
||||||
std::queue<PacketInfo> capacity_link_ RTC_GUARDED_BY(process_checker_);
|
std::queue<PacketInfo> capacity_link_ RTC_GUARDED_BY(process_checker_);
|
||||||
// Models the extra delay of the network (see `queue_delay_ms`
|
// Models the extra delay of the network (see `queue_delay_ms`
|
||||||
// and `delay_standard_deviation_ms` in BuiltInNetworkBehaviorConfig), packets
|
// and `delay_standard_deviation_ms` in BuiltInNetworkBehaviorConfig), packets
|
||||||
|
@ -110,8 +133,10 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
// Represents the next moment in time when the network is supposed to deliver
|
// Represents the next moment in time when the network is supposed to deliver
|
||||||
// packets to the client (either by pulling them from `delay_link_` or
|
// packets to the client (either by pulling them from `delay_link_` or
|
||||||
// `capacity_link_` or both).
|
// `capacity_link_` or both).
|
||||||
absl::optional<int64_t> next_process_time_us_
|
Timestamp next_process_time_ RTC_GUARDED_BY(process_checker_) =
|
||||||
RTC_GUARDED_BY(process_checker_);
|
Timestamp::PlusInfinity();
|
||||||
|
absl::AnyInvocable<void()> next_process_time_changed_callback_
|
||||||
|
RTC_GUARDED_BY(process_checker_) = nullptr;
|
||||||
|
|
||||||
ConfigState config_state_ RTC_GUARDED_BY(config_lock_);
|
ConfigState config_state_ RTC_GUARDED_BY(config_lock_);
|
||||||
|
|
||||||
|
@ -126,7 +151,7 @@ class RTC_EXPORT SimulatedNetwork : public SimulatedNetworkInterface {
|
||||||
// The last time a packet left the capacity_link_ (used to enforce
|
// The last time a packet left the capacity_link_ (used to enforce
|
||||||
// the capacity of the link and avoid packets starts to get sent before
|
// the capacity of the link and avoid packets starts to get sent before
|
||||||
// the link it free).
|
// the link it free).
|
||||||
int64_t last_capacity_link_exit_time_;
|
Timestamp last_capacity_link_exit_time_ = Timestamp::MinusInfinity();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "test/network/simulated_network.h"
|
#include "test/network/simulated_network.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
#include "api/test/simulated_network.h"
|
#include "api/test/simulated_network.h"
|
||||||
#include "api/units/data_rate.h"
|
#include "api/units/data_rate.h"
|
||||||
#include "api/units/time_delta.h"
|
#include "api/units/time_delta.h"
|
||||||
|
#include "api/units/timestamp.h"
|
||||||
#include "test/gmock.h"
|
#include "test/gmock.h"
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
|
|
||||||
|
@ -26,6 +28,8 @@ namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
|
using ::testing::MockFunction;
|
||||||
|
using ::testing::SizeIs;
|
||||||
|
|
||||||
PacketInFlightInfo PacketWithSize(size_t size) {
|
PacketInFlightInfo PacketWithSize(size_t size) {
|
||||||
return PacketInFlightInfo(/*size=*/size, /*send_time_us=*/0, /*packet_id=*/1);
|
return PacketInFlightInfo(/*size=*/size, /*send_time_us=*/0, /*packet_id=*/1);
|
||||||
|
@ -194,10 +198,115 @@ TEST(SimulatedNetworkTest,
|
||||||
/*receive_time_us=*/TimeDelta::Millis(1100).us())));
|
/*receive_time_us=*/TimeDelta::Millis(1100).us())));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, NetworkEmptyAfterLastPacketDequeued) {
|
TEST(SimulatedNetworkTest,
|
||||||
|
SetConfigUpdateNextDeliveryTimeIfLinkCapacityChange) {
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
||||||
// should be ready to exit the network in 1 second.
|
// should be ready to exit the network in 1 second.
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
|
MockFunction<void()> delivery_time_changed_callback;
|
||||||
|
network.RegisterDeliveryTimeChangedCallback(
|
||||||
|
delivery_time_changed_callback.AsStdFunction());
|
||||||
|
const PacketInFlightInfo packet_1 =
|
||||||
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1);
|
||||||
|
ASSERT_TRUE(network.EnqueuePacket(packet_1));
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Seconds(1).us());
|
||||||
|
|
||||||
|
// Since the link capacity changes from 1 kbps to 10 kbps, packets will take
|
||||||
|
// 100 ms each to leave the network. After 500ms, half the packet should have
|
||||||
|
// gone through.
|
||||||
|
EXPECT_CALL(delivery_time_changed_callback, Call).WillOnce([&]() {
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Millis(500 + 50).us());
|
||||||
|
});
|
||||||
|
network.SetConfig({.link_capacity_kbps = 10},
|
||||||
|
/*config_update_time*/ Timestamp::Millis(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SimulatedNetworkTest,
|
||||||
|
SetConfigUpdateNextDeliveryTimeIfLinkCapacityChangeFromZero) {
|
||||||
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 0});
|
||||||
|
MockFunction<void()> delivery_time_changed_callback;
|
||||||
|
network.RegisterDeliveryTimeChangedCallback(
|
||||||
|
delivery_time_changed_callback.AsStdFunction());
|
||||||
|
const PacketInFlightInfo packet_1 =
|
||||||
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1);
|
||||||
|
ASSERT_TRUE(network.EnqueuePacket(packet_1));
|
||||||
|
EXPECT_FALSE(network.NextDeliveryTimeUs().has_value());
|
||||||
|
|
||||||
|
// The link capacity changes from 0 kbps to 10 kbps during 10ms 1/10th of the
|
||||||
|
// packet will be transmitted. (The packet would take 100ms to go through the
|
||||||
|
// network at 10kbps.)
|
||||||
|
::testing::Sequence s;
|
||||||
|
EXPECT_CALL(delivery_time_changed_callback, Call)
|
||||||
|
.InSequence(s)
|
||||||
|
.WillOnce([&]() {
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(),
|
||||||
|
TimeDelta::Millis(500 + 100).us());
|
||||||
|
});
|
||||||
|
EXPECT_CALL(delivery_time_changed_callback, Call)
|
||||||
|
.InSequence(s)
|
||||||
|
.WillOnce(
|
||||||
|
[&]() { EXPECT_FALSE(network.NextDeliveryTimeUs().has_value()); });
|
||||||
|
EXPECT_CALL(delivery_time_changed_callback, Call)
|
||||||
|
.InSequence(s)
|
||||||
|
.WillOnce([&]() {
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(),
|
||||||
|
TimeDelta::Millis(610 + 90).us());
|
||||||
|
});
|
||||||
|
network.SetConfig({.link_capacity_kbps = 10},
|
||||||
|
/*config_update_time*/ Timestamp::Millis(500));
|
||||||
|
network.SetConfig({.link_capacity_kbps = 0},
|
||||||
|
/*config_update_time*/ Timestamp::Millis(510));
|
||||||
|
network.SetConfig({.link_capacity_kbps = 10},
|
||||||
|
/*config_update_time*/ Timestamp::Millis(610));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SimulatedNetworkTest, SetConfigUpdateQueueDelayAfterDelivery) {
|
||||||
|
// A packet of 125 bytes that gets enqueued on a network with 1000 kbps
|
||||||
|
// capacity should be ready to exit the narrow section in 1 ms.
|
||||||
|
SimulatedNetwork network =
|
||||||
|
SimulatedNetwork({.queue_delay_ms = 1000, .link_capacity_kbps = 1000});
|
||||||
|
MockFunction<void()> delivery_time_changed_callback;
|
||||||
|
network.RegisterDeliveryTimeChangedCallback(
|
||||||
|
delivery_time_changed_callback.AsStdFunction());
|
||||||
|
EXPECT_CALL(delivery_time_changed_callback, Call).Times(0);
|
||||||
|
const PacketInFlightInfo packet_1 =
|
||||||
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1);
|
||||||
|
ASSERT_TRUE(network.EnqueuePacket(packet_1));
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Millis(1).us());
|
||||||
|
// But no packets is actually delivered. Only moved to the delay link.
|
||||||
|
EXPECT_TRUE(network
|
||||||
|
.DequeueDeliverablePackets(
|
||||||
|
/*receive_time_us=*/TimeDelta::Millis(1).us())
|
||||||
|
.empty());
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Millis(1000 + 1).us());
|
||||||
|
|
||||||
|
// Changing the queue time does not change the next delivery time.
|
||||||
|
network.SetConfig({.queue_delay_ms = 1, .link_capacity_kbps = 100},
|
||||||
|
/*config_update_time*/ Timestamp::Millis(500));
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Millis(1000 + 1).us());
|
||||||
|
|
||||||
|
// A new packet require NextDeliveryTimeUs to change since the capacity
|
||||||
|
// change. But does not affect the delivery time of packet_1.
|
||||||
|
const PacketInFlightInfo packet_2 = PacketInFlightInfo(
|
||||||
|
/*size=*/125, /*send_time_us=*/TimeDelta::Millis(500).us(),
|
||||||
|
/*packet_id=*/2);
|
||||||
|
ASSERT_TRUE(network.EnqueuePacket(packet_2));
|
||||||
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Millis(1000 + 1).us());
|
||||||
|
// At 100kbps, it will take packet 2 10ms to pass through the narrow section.
|
||||||
|
// Since delay is lower for packet_2, but reordering is not allowed, both
|
||||||
|
// packets are delivered at the same time.
|
||||||
|
std::vector<PacketDeliveryInfo> delivered_packets =
|
||||||
|
network.DequeueDeliverablePackets(
|
||||||
|
/*receive_time_us=*/TimeDelta::Millis(1000 + 1).us());
|
||||||
|
ASSERT_THAT(delivered_packets, SizeIs(2));
|
||||||
|
EXPECT_EQ(delivered_packets[0].receive_time_us,
|
||||||
|
delivered_packets[1].receive_time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SimulatedNetworkTest, NetworkEmptyAfterLastPacketDequeued) {
|
||||||
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
|
// capacity should be ready to exit the network in 1 second.
|
||||||
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
||||||
|
|
||||||
// Collecting all the delivered packets ...
|
// Collecting all the delivered packets ...
|
||||||
|
@ -211,21 +320,21 @@ TEST(SimulatedNetworkTest, NetworkEmptyAfterLastPacketDequeued) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, DequeueDeliverablePacketsOnLateCall) {
|
TEST(SimulatedNetworkTest, DequeueDeliverablePacketsOnLateCall) {
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network in 1 second.
|
// capacity should be ready to exit the network in 1 second.
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(
|
||||||
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
||||||
|
|
||||||
// Enqueue another packet of 125 bytes with send time 1 second so this should
|
// Enqueue another packet of 125 bytes with send time 1 second so this
|
||||||
// exit after 2 seconds.
|
// should exit after 2 seconds.
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(
|
||||||
PacketInFlightInfo(/*size=*/125,
|
PacketInFlightInfo(/*size=*/125,
|
||||||
/*send_time_us=*/TimeDelta::Seconds(1).us(),
|
/*send_time_us=*/TimeDelta::Seconds(1).us(),
|
||||||
/*packet_id=*/2)));
|
/*packet_id=*/2)));
|
||||||
|
|
||||||
// Collecting delivered packets after 3 seconds will result in the delivery of
|
// Collecting delivered packets after 3 seconds will result in the delivery
|
||||||
// both the enqueued packets.
|
// of both the enqueued packets.
|
||||||
std::vector<PacketDeliveryInfo> delivered_packets =
|
std::vector<PacketDeliveryInfo> delivered_packets =
|
||||||
network.DequeueDeliverablePackets(
|
network.DequeueDeliverablePackets(
|
||||||
/*receive_time_us=*/TimeDelta::Seconds(3).us());
|
/*receive_time_us=*/TimeDelta::Seconds(3).us());
|
||||||
|
@ -234,13 +343,13 @@ TEST(SimulatedNetworkTest, DequeueDeliverablePacketsOnLateCall) {
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest,
|
TEST(SimulatedNetworkTest,
|
||||||
DequeueDeliverablePacketsOnEarlyCallReturnsNoPackets) {
|
DequeueDeliverablePacketsOnEarlyCallReturnsNoPackets) {
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network in 1 second.
|
// capacity should be ready to exit the network in 1 second.
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
||||||
|
|
||||||
// Collecting delivered packets after 0.5 seconds will result in the delivery
|
// Collecting delivered packets after 0.5 seconds will result in the
|
||||||
// of 0 packets.
|
// delivery of 0 packets.
|
||||||
std::vector<PacketDeliveryInfo> delivered_packets =
|
std::vector<PacketDeliveryInfo> delivered_packets =
|
||||||
network.DequeueDeliverablePackets(
|
network.DequeueDeliverablePackets(
|
||||||
/*receive_time_us=*/TimeDelta::Seconds(0.5).us());
|
/*receive_time_us=*/TimeDelta::Seconds(0.5).us());
|
||||||
|
@ -251,8 +360,8 @@ TEST(SimulatedNetworkTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, QueueDelayMsWithoutStandardDeviation) {
|
TEST(SimulatedNetworkTest, QueueDelayMsWithoutStandardDeviation) {
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network in 1 second.
|
// capacity should be ready to exit the network in 1 second.
|
||||||
SimulatedNetwork network =
|
SimulatedNetwork network =
|
||||||
SimulatedNetwork({.queue_delay_ms = 100, .link_capacity_kbps = 1});
|
SimulatedNetwork({.queue_delay_ms = 100, .link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
ASSERT_TRUE(network.EnqueuePacket(PacketWithSize(125)));
|
||||||
|
@ -283,8 +392,8 @@ TEST(SimulatedNetworkTest,
|
||||||
.delay_standard_deviation_ms = 90,
|
.delay_standard_deviation_ms = 90,
|
||||||
.link_capacity_kbps = 1,
|
.link_capacity_kbps = 1,
|
||||||
.allow_reordering = false});
|
.allow_reordering = false});
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network in 1 second.
|
// capacity should be ready to exit the network in 1 second.
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(
|
||||||
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
||||||
|
|
||||||
|
@ -322,8 +431,8 @@ TEST(SimulatedNetworkTest, QueueDelayMsWithStandardDeviationAndReorderAllowed) {
|
||||||
.link_capacity_kbps = 1,
|
.link_capacity_kbps = 1,
|
||||||
.allow_reordering = true},
|
.allow_reordering = true},
|
||||||
/*random_seed=*/1);
|
/*random_seed=*/1);
|
||||||
// A packet of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// A packet of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network in 1 second.
|
// capacity should be ready to exit the network in 1 second.
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(
|
||||||
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
||||||
|
|
||||||
|
@ -356,7 +465,8 @@ TEST(SimulatedNetworkTest, QueueDelayMsWithStandardDeviationAndReorderAllowed) {
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, PacketLoss) {
|
TEST(SimulatedNetworkTest, PacketLoss) {
|
||||||
// On a network with 50% probablility of packet loss ...
|
// On a network with 50% probablility of packet loss ...
|
||||||
SimulatedNetwork network = SimulatedNetwork({.loss_percent = 50});
|
SimulatedNetwork network =
|
||||||
|
SimulatedNetwork({.loss_percent = 50}, /*random_seed =*/1);
|
||||||
|
|
||||||
// Enqueueing 8 packets ...
|
// Enqueueing 8 packets ...
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
@ -379,9 +489,49 @@ TEST(SimulatedNetworkTest, PacketLoss) {
|
||||||
EXPECT_EQ(lost_packets, 4);
|
EXPECT_EQ(lost_packets, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SimulatedNetworkTest, NextDeliveryTimeSetAfterLostPackets) {
|
||||||
|
// On a network with 50% probablility of packet loss ...
|
||||||
|
SimulatedNetwork network = SimulatedNetwork(
|
||||||
|
{.queue_delay_ms = 10, .link_capacity_kbps = 1000, .loss_percent = 50},
|
||||||
|
/*random_seed =*/1);
|
||||||
|
// Enqueueing 8 packets at the same time. It should take 1ms to pass through
|
||||||
|
// the capacity limited section per packet, it total adding 8ms delay to the
|
||||||
|
// last packet. Since queue delay is 10ms, multiple packets will be in the
|
||||||
|
// delay queue at the same time.
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
ASSERT_TRUE(network.EnqueuePacket(PacketInFlightInfo(
|
||||||
|
/*size=*/125, /*send_time_us=*/0, /*packet_id=*/i + 1)));
|
||||||
|
}
|
||||||
|
int64_t time_us = 0;
|
||||||
|
std::vector<PacketDeliveryInfo> delivered_packets;
|
||||||
|
// This assumes first packet is lost and last packet is delivered....
|
||||||
|
while (delivered_packets.size() != 8) {
|
||||||
|
ASSERT_TRUE(network.NextDeliveryTimeUs().has_value());
|
||||||
|
time_us = *network.NextDeliveryTimeUs();
|
||||||
|
std::vector<PacketDeliveryInfo> packets =
|
||||||
|
network.DequeueDeliverablePackets(time_us);
|
||||||
|
delivered_packets.insert(delivered_packets.end(), packets.begin(),
|
||||||
|
packets.end());
|
||||||
|
}
|
||||||
|
// Results in the loss of 4 of them.
|
||||||
|
int lost_packets = 0;
|
||||||
|
int received_packets = 0;
|
||||||
|
for (const auto& packet : delivered_packets) {
|
||||||
|
if (packet.receive_time_us == PacketDeliveryInfo::kNotReceived) {
|
||||||
|
lost_packets++;
|
||||||
|
} else {
|
||||||
|
received_packets++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_EQ(delivered_packets.back().receive_time_us,
|
||||||
|
Timestamp::Millis(10 + 8).us());
|
||||||
|
EXPECT_EQ(lost_packets, 4);
|
||||||
|
EXPECT_EQ(received_packets, 4);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, PacketLossBurst) {
|
TEST(SimulatedNetworkTest, PacketLossBurst) {
|
||||||
// On a network with 50% probablility of packet loss and an average burst loss
|
// On a network with 50% probablility of packet loss and an average burst
|
||||||
// length of 100 ...
|
// loss length of 100 ...
|
||||||
SimulatedNetwork network = SimulatedNetwork(
|
SimulatedNetwork network = SimulatedNetwork(
|
||||||
{.loss_percent = 50, .avg_burst_loss_length = 100}, /*random_seed=*/1);
|
{.loss_percent = 50, .avg_burst_loss_length = 100}, /*random_seed=*/1);
|
||||||
|
|
||||||
|
@ -412,8 +562,9 @@ TEST(SimulatedNetworkTest, PacketLossBurst) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, PauseTransmissionUntil) {
|
TEST(SimulatedNetworkTest, PauseTransmissionUntil) {
|
||||||
// 3 packets of 125 bytes that gets enqueued on a network with 1 kbps capacity
|
// 3 packets of 125 bytes that gets enqueued on a network with 1 kbps
|
||||||
// should be ready to exit the network after 1, 2 and 3 seconds respectively.
|
// capacity should be ready to exit the network after 1, 2 and 3 seconds
|
||||||
|
// respectively.
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(
|
||||||
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/1)));
|
||||||
|
@ -443,8 +594,8 @@ TEST(SimulatedNetworkTest, PauseTransmissionUntil) {
|
||||||
// delivery time of the next packet which accounts for the network pause.
|
// delivery time of the next packet which accounts for the network pause.
|
||||||
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Seconds(6).us());
|
EXPECT_EQ(network.NextDeliveryTimeUs(), TimeDelta::Seconds(6).us());
|
||||||
|
|
||||||
// And 2 seconds after the exit of the first enqueued packet, the following 2
|
// And 2 seconds after the exit of the first enqueued packet, the following
|
||||||
// packets are also delivered.
|
// 2 packets are also delivered.
|
||||||
delivered_packets = network.DequeueDeliverablePackets(
|
delivered_packets = network.DequeueDeliverablePackets(
|
||||||
/*receive_time_us=*/TimeDelta::Seconds(7).us());
|
/*receive_time_us=*/TimeDelta::Seconds(7).us());
|
||||||
EXPECT_EQ(delivered_packets.size(), 2ul);
|
EXPECT_EQ(delivered_packets.size(), 2ul);
|
||||||
|
@ -453,8 +604,8 @@ TEST(SimulatedNetworkTest, PauseTransmissionUntil) {
|
||||||
TEST(SimulatedNetworkTest, CongestedNetworkRespectsLinkCapacity) {
|
TEST(SimulatedNetworkTest, CongestedNetworkRespectsLinkCapacity) {
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
for (size_t i = 0; i < 1'000; ++i) {
|
for (size_t i = 0; i < 1'000; ++i) {
|
||||||
ASSERT_TRUE(network.EnqueuePacket(
|
ASSERT_TRUE(network.EnqueuePacket(PacketInFlightInfo(
|
||||||
PacketInFlightInfo(/*size=*/125, /*send_time_us=*/0, /*packet_id=*/i)));
|
/*size=*/125, /*send_time_us=*/0, /*packet_id=*/i)));
|
||||||
}
|
}
|
||||||
PacketDeliveryInfo last_delivered_packet{
|
PacketDeliveryInfo last_delivered_packet{
|
||||||
PacketInFlightInfo(/*size=*/0, /*send_time_us=*/0, /*packet_id=*/0), 0};
|
PacketInFlightInfo(/*size=*/0, /*send_time_us=*/0, /*packet_id=*/0), 0};
|
||||||
|
@ -474,10 +625,10 @@ TEST(SimulatedNetworkTest, CongestedNetworkRespectsLinkCapacity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SimulatedNetworkTest, EnqueuePacketWithSubSecondNonMonotonicBehaviour) {
|
TEST(SimulatedNetworkTest, EnqueuePacketWithSubSecondNonMonotonicBehaviour) {
|
||||||
// On multi-core systems, different threads can experience sub-millisecond non
|
// On multi-core systems, different threads can experience sub-millisecond
|
||||||
// monothonic behaviour when running on different cores. This test checks that
|
// non monothonic behaviour when running on different cores. This test
|
||||||
// when a non monotonic packet enqueue, the network continues to work and the
|
// checks that when a non monotonic packet enqueue, the network continues to
|
||||||
// out of order packet is sent anyway.
|
// work and the out of order packet is sent anyway.
|
||||||
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
SimulatedNetwork network = SimulatedNetwork({.link_capacity_kbps = 1});
|
||||||
ASSERT_TRUE(network.EnqueuePacket(PacketInFlightInfo(
|
ASSERT_TRUE(network.EnqueuePacket(PacketInFlightInfo(
|
||||||
/*size=*/125, /*send_time_us=*/TimeDelta::Seconds(1).us(),
|
/*size=*/125, /*send_time_us=*/TimeDelta::Seconds(1).us(),
|
||||||
|
|
|
@ -24,7 +24,7 @@ constexpr char kDummyTransportName[] = "dummy";
|
||||||
SimulatedNetwork::Config CreateSimulationConfig(
|
SimulatedNetwork::Config CreateSimulationConfig(
|
||||||
NetworkSimulationConfig config) {
|
NetworkSimulationConfig config) {
|
||||||
SimulatedNetwork::Config sim_config;
|
SimulatedNetwork::Config sim_config;
|
||||||
sim_config.link_capacity_kbps = config.bandwidth.kbps_or(0);
|
sim_config.link_capacity_kbps = config.bandwidth.kbps_or(-1);
|
||||||
sim_config.loss_percent = config.loss_rate * 100;
|
sim_config.loss_percent = config.loss_rate * 100;
|
||||||
sim_config.queue_delay_ms = config.delay.ms();
|
sim_config.queue_delay_ms = config.delay.ms();
|
||||||
sim_config.delay_standard_deviation_ms = config.delay_std_dev.ms();
|
sim_config.delay_standard_deviation_ms = config.delay_std_dev.ms();
|
||||||
|
|
Loading…
Reference in a new issue