| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quiche/quic/test_tools/simulator/simulator.h" |
| |
| #include <utility> |
| |
| #include "absl/container/node_hash_map.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/quic_test_utils.h" |
| #include "quiche/quic/test_tools/simulator/alarm_factory.h" |
| #include "quiche/quic/test_tools/simulator/link.h" |
| #include "quiche/quic/test_tools/simulator/packet_filter.h" |
| #include "quiche/quic/test_tools/simulator/queue.h" |
| #include "quiche/quic/test_tools/simulator/switch.h" |
| #include "quiche/quic/test_tools/simulator/traffic_policer.h" |
| |
| using testing::_; |
| using testing::Return; |
| using testing::StrictMock; |
| |
| namespace quic { |
| namespace simulator { |
| |
| // A simple counter that increments its value by 1 every specified period. |
| class Counter : public Actor { |
| public: |
| Counter(Simulator* simulator, std::string name, QuicTime::Delta period) |
| : Actor(simulator, name), value_(-1), period_(period) { |
| Schedule(clock_->Now()); |
| } |
| ~Counter() override {} |
| |
| inline int get_value() const { return value_; } |
| |
| void Act() override { |
| ++value_; |
| QUIC_DVLOG(1) << name_ << " has value " << value_ << " at time " |
| << clock_->Now().ToDebuggingValue(); |
| Schedule(clock_->Now() + period_); |
| } |
| |
| private: |
| int value_; |
| QuicTime::Delta period_; |
| }; |
| |
| class SimulatorTest : public quic::test::QuicTest {}; |
| |
| // Test that the basic event handling works, and that Actors can be created and |
| // destroyed mid-simulation. |
| TEST_F(SimulatorTest, Counters) { |
| Simulator simulator; |
| for (int i = 0; i < 2; ++i) { |
| Counter fast_counter(&simulator, "fast_counter", |
| QuicTime::Delta::FromSeconds(3)); |
| Counter slow_counter(&simulator, "slow_counter", |
| QuicTime::Delta::FromSeconds(10)); |
| |
| simulator.RunUntil( |
| [&slow_counter]() { return slow_counter.get_value() >= 10; }); |
| |
| EXPECT_EQ(10, slow_counter.get_value()); |
| EXPECT_EQ(10 * 10 / 3, fast_counter.get_value()); |
| } |
| } |
| |
| // A port which counts the number of packets received on it, both total and |
| // per-destination. |
| class CounterPort : public UnconstrainedPortInterface { |
| public: |
| CounterPort() { Reset(); } |
| ~CounterPort() override {} |
| |
| inline QuicByteCount bytes() const { return bytes_; } |
| inline QuicPacketCount packets() const { return packets_; } |
| |
| void AcceptPacket(std::unique_ptr<Packet> packet) override { |
| bytes_ += packet->size; |
| packets_ += 1; |
| |
| per_destination_packet_counter_[packet->destination] += 1; |
| } |
| |
| void Reset() { |
| bytes_ = 0; |
| packets_ = 0; |
| per_destination_packet_counter_.clear(); |
| } |
| |
| QuicPacketCount CountPacketsForDestination(std::string destination) const { |
| auto result_it = per_destination_packet_counter_.find(destination); |
| if (result_it == per_destination_packet_counter_.cend()) { |
| return 0; |
| } |
| return result_it->second; |
| } |
| |
| private: |
| QuicByteCount bytes_; |
| QuicPacketCount packets_; |
| |
| absl::node_hash_map<std::string, QuicPacketCount> |
| per_destination_packet_counter_; |
| }; |
| |
| // Sends the packet to the specified destination at the uplink rate. Provides a |
| // CounterPort as an Rx interface. |
| class LinkSaturator : public Endpoint { |
| public: |
| LinkSaturator(Simulator* simulator, std::string name, |
| QuicByteCount packet_size, std::string destination) |
| : Endpoint(simulator, name), |
| packet_size_(packet_size), |
| destination_(std::move(destination)), |
| bytes_transmitted_(0), |
| packets_transmitted_(0) { |
| Schedule(clock_->Now()); |
| } |
| |
| void Act() override { |
| if (tx_port_->TimeUntilAvailable().IsZero()) { |
| auto packet = std::make_unique<Packet>(); |
| packet->source = name_; |
| packet->destination = destination_; |
| packet->tx_timestamp = clock_->Now(); |
| packet->size = packet_size_; |
| |
| tx_port_->AcceptPacket(std::move(packet)); |
| |
| bytes_transmitted_ += packet_size_; |
| packets_transmitted_ += 1; |
| } |
| |
| Schedule(clock_->Now() + tx_port_->TimeUntilAvailable()); |
| } |
| |
| UnconstrainedPortInterface* GetRxPort() override { |
| return static_cast<UnconstrainedPortInterface*>(&rx_port_); |
| } |
| |
| void SetTxPort(ConstrainedPortInterface* port) override { tx_port_ = port; } |
| |
| CounterPort* counter() { return &rx_port_; } |
| |
| inline QuicByteCount bytes_transmitted() const { return bytes_transmitted_; } |
| inline QuicPacketCount packets_transmitted() const { |
| return packets_transmitted_; |
| } |
| |
| void Pause() { Unschedule(); } |
| void Resume() { Schedule(clock_->Now()); } |
| |
| private: |
| QuicByteCount packet_size_; |
| std::string destination_; |
| |
| ConstrainedPortInterface* tx_port_; |
| CounterPort rx_port_; |
| |
| QuicByteCount bytes_transmitted_; |
| QuicPacketCount packets_transmitted_; |
| }; |
| |
| // Saturate a symmetric link and verify that the number of packets sent and |
| // received is correct. |
| TEST_F(SimulatorTest, DirectLinkSaturation) { |
| Simulator simulator; |
| LinkSaturator saturator_a(&simulator, "Saturator A", 1000, "Saturator B"); |
| LinkSaturator saturator_b(&simulator, "Saturator B", 100, "Saturator A"); |
| SymmetricLink link(&saturator_a, &saturator_b, |
| QuicBandwidth::FromKBytesPerSecond(1000), |
| QuicTime::Delta::FromMilliseconds(100) + |
| QuicTime::Delta::FromMicroseconds(1)); |
| |
| const QuicTime start_time = simulator.GetClock()->Now(); |
| const QuicTime after_first_50_ms = |
| start_time + QuicTime::Delta::FromMilliseconds(50); |
| simulator.RunUntil([&simulator, after_first_50_ms]() { |
| return simulator.GetClock()->Now() >= after_first_50_ms; |
| }); |
| EXPECT_LE(1000u * 50u, saturator_a.bytes_transmitted()); |
| EXPECT_GE(1000u * 51u, saturator_a.bytes_transmitted()); |
| EXPECT_LE(1000u * 50u, saturator_b.bytes_transmitted()); |
| EXPECT_GE(1000u * 51u, saturator_b.bytes_transmitted()); |
| EXPECT_LE(50u, saturator_a.packets_transmitted()); |
| EXPECT_GE(51u, saturator_a.packets_transmitted()); |
| EXPECT_LE(500u, saturator_b.packets_transmitted()); |
| EXPECT_GE(501u, saturator_b.packets_transmitted()); |
| EXPECT_EQ(0u, saturator_a.counter()->bytes()); |
| EXPECT_EQ(0u, saturator_b.counter()->bytes()); |
| |
| simulator.RunUntil([&saturator_a, &saturator_b]() { |
| if (saturator_a.counter()->packets() > 1000 || |
| saturator_b.counter()->packets() > 100) { |
| ADD_FAILURE() << "The simulation did not arrive at the expected " |
| "termination contidition. Saturator A counter: " |
| << saturator_a.counter()->packets() |
| << ", saturator B counter: " |
| << saturator_b.counter()->packets(); |
| return true; |
| } |
| |
| return saturator_a.counter()->packets() == 1000 && |
| saturator_b.counter()->packets() == 100; |
| }); |
| EXPECT_EQ(201u, saturator_a.packets_transmitted()); |
| EXPECT_EQ(2001u, saturator_b.packets_transmitted()); |
| EXPECT_EQ(201u * 1000, saturator_a.bytes_transmitted()); |
| EXPECT_EQ(2001u * 100, saturator_b.bytes_transmitted()); |
| |
| EXPECT_EQ(1000u, |
| saturator_a.counter()->CountPacketsForDestination("Saturator A")); |
| EXPECT_EQ(100u, |
| saturator_b.counter()->CountPacketsForDestination("Saturator B")); |
| EXPECT_EQ(0u, |
| saturator_a.counter()->CountPacketsForDestination("Saturator B")); |
| EXPECT_EQ(0u, |
| saturator_b.counter()->CountPacketsForDestination("Saturator A")); |
| |
| const QuicTime end_time = simulator.GetClock()->Now(); |
| const QuicBandwidth observed_bandwidth = QuicBandwidth::FromBytesAndTimeDelta( |
| saturator_a.bytes_transmitted(), end_time - start_time); |
| EXPECT_APPROX_EQ(link.bandwidth(), observed_bandwidth, 0.01f); |
| } |
| |
| // Accepts packets and stores them internally. |
| class PacketAcceptor : public ConstrainedPortInterface { |
| public: |
| void AcceptPacket(std::unique_ptr<Packet> packet) override { |
| packets_.emplace_back(std::move(packet)); |
| } |
| |
| QuicTime::Delta TimeUntilAvailable() override { |
| return QuicTime::Delta::Zero(); |
| } |
| |
| std::vector<std::unique_ptr<Packet>>* packets() { return &packets_; } |
| |
| private: |
| std::vector<std::unique_ptr<Packet>> packets_; |
| }; |
| |
| // Ensure the queue behaves correctly with accepting packets. |
| TEST_F(SimulatorTest, Queue) { |
| Simulator simulator; |
| Queue queue(&simulator, "Queue", 1000); |
| PacketAcceptor acceptor; |
| queue.set_tx_port(&acceptor); |
| |
| EXPECT_EQ(0u, queue.bytes_queued()); |
| EXPECT_EQ(0u, queue.packets_queued()); |
| EXPECT_EQ(0u, acceptor.packets()->size()); |
| |
| auto first_packet = std::make_unique<Packet>(); |
| first_packet->size = 600; |
| queue.AcceptPacket(std::move(first_packet)); |
| EXPECT_EQ(600u, queue.bytes_queued()); |
| EXPECT_EQ(1u, queue.packets_queued()); |
| EXPECT_EQ(0u, acceptor.packets()->size()); |
| |
| // The second packet does not fit and is dropped. |
| auto second_packet = std::make_unique<Packet>(); |
| second_packet->size = 500; |
| queue.AcceptPacket(std::move(second_packet)); |
| EXPECT_EQ(600u, queue.bytes_queued()); |
| EXPECT_EQ(1u, queue.packets_queued()); |
| EXPECT_EQ(0u, acceptor.packets()->size()); |
| |
| auto third_packet = std::make_unique<Packet>(); |
| third_packet->size = 400; |
| queue.AcceptPacket(std::move(third_packet)); |
| EXPECT_EQ(1000u, queue.bytes_queued()); |
| EXPECT_EQ(2u, queue.packets_queued()); |
| EXPECT_EQ(0u, acceptor.packets()->size()); |
| |
| // Run until there is nothing scheduled, so that the queue can deplete. |
| simulator.RunUntil([]() { return false; }); |
| EXPECT_EQ(0u, queue.bytes_queued()); |
| EXPECT_EQ(0u, queue.packets_queued()); |
| ASSERT_EQ(2u, acceptor.packets()->size()); |
| EXPECT_EQ(600u, acceptor.packets()->at(0)->size); |
| EXPECT_EQ(400u, acceptor.packets()->at(1)->size); |
| } |
| |
| // Simulate a situation where the bottleneck link is 10 times slower than the |
| // uplink, and they are separated by a queue. |
| TEST_F(SimulatorTest, QueueBottleneck) { |
| const QuicBandwidth local_bandwidth = |
| QuicBandwidth::FromKBytesPerSecond(1000); |
| const QuicBandwidth bottleneck_bandwidth = 0.1f * local_bandwidth; |
| const QuicTime::Delta local_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(1); |
| const QuicTime::Delta bottleneck_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(20); |
| const QuicByteCount bdp = |
| bottleneck_bandwidth * |
| (local_propagation_delay + bottleneck_propagation_delay); |
| |
| Simulator simulator; |
| LinkSaturator saturator(&simulator, "Saturator", 1000, "Counter"); |
| ASSERT_GE(bdp, 1000u); |
| Queue queue(&simulator, "Queue", bdp); |
| CounterPort counter; |
| |
| OneWayLink local_link(&simulator, "Local link", &queue, local_bandwidth, |
| local_propagation_delay); |
| OneWayLink bottleneck_link(&simulator, "Bottleneck link", &counter, |
| bottleneck_bandwidth, |
| bottleneck_propagation_delay); |
| saturator.SetTxPort(&local_link); |
| queue.set_tx_port(&bottleneck_link); |
| |
| static const QuicPacketCount packets_received = 1000; |
| simulator.RunUntil( |
| [&counter]() { return counter.packets() == packets_received; }); |
| const double loss_ratio = 1 - static_cast<double>(packets_received) / |
| saturator.packets_transmitted(); |
| EXPECT_NEAR(loss_ratio, 0.9, 0.001); |
| } |
| |
| // Verify that the queue of exactly one packet allows the transmission to |
| // actually go through. |
| TEST_F(SimulatorTest, OnePacketQueue) { |
| const QuicBandwidth local_bandwidth = |
| QuicBandwidth::FromKBytesPerSecond(1000); |
| const QuicBandwidth bottleneck_bandwidth = 0.1f * local_bandwidth; |
| const QuicTime::Delta local_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(1); |
| const QuicTime::Delta bottleneck_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(20); |
| |
| Simulator simulator; |
| LinkSaturator saturator(&simulator, "Saturator", 1000, "Counter"); |
| Queue queue(&simulator, "Queue", 1000); |
| CounterPort counter; |
| |
| OneWayLink local_link(&simulator, "Local link", &queue, local_bandwidth, |
| local_propagation_delay); |
| OneWayLink bottleneck_link(&simulator, "Bottleneck link", &counter, |
| bottleneck_bandwidth, |
| bottleneck_propagation_delay); |
| saturator.SetTxPort(&local_link); |
| queue.set_tx_port(&bottleneck_link); |
| |
| static const QuicPacketCount packets_received = 10; |
| // The deadline here is to prevent this tests from looping infinitely in case |
| // the packets never reach the receiver. |
| const QuicTime deadline = |
| simulator.GetClock()->Now() + QuicTime::Delta::FromSeconds(10); |
| simulator.RunUntil([&simulator, &counter, deadline]() { |
| return counter.packets() == packets_received || |
| simulator.GetClock()->Now() > deadline; |
| }); |
| ASSERT_EQ(packets_received, counter.packets()); |
| } |
| |
| // Simulate a network where three endpoints are connected to a switch and they |
| // are sending traffic in circle (1 -> 2, 2 -> 3, 3 -> 1). |
| TEST_F(SimulatorTest, SwitchedNetwork) { |
| const QuicBandwidth bandwidth = QuicBandwidth::FromBytesPerSecond(10000); |
| const QuicTime::Delta base_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(50); |
| |
| Simulator simulator; |
| LinkSaturator saturator1(&simulator, "Saturator 1", 1000, "Saturator 2"); |
| LinkSaturator saturator2(&simulator, "Saturator 2", 1000, "Saturator 3"); |
| LinkSaturator saturator3(&simulator, "Saturator 3", 1000, "Saturator 1"); |
| Switch network_switch(&simulator, "Switch", 8, |
| bandwidth * base_propagation_delay * 10); |
| |
| // For determinicity, make it so that the first packet will arrive from |
| // Saturator 1, then from Saturator 2, and then from Saturator 3. |
| SymmetricLink link1(&saturator1, network_switch.port(1), bandwidth, |
| base_propagation_delay); |
| SymmetricLink link2(&saturator2, network_switch.port(2), bandwidth, |
| base_propagation_delay * 2); |
| SymmetricLink link3(&saturator3, network_switch.port(3), bandwidth, |
| base_propagation_delay * 3); |
| |
| const QuicTime start_time = simulator.GetClock()->Now(); |
| static const QuicPacketCount bytes_received = 64 * 1000; |
| simulator.RunUntil([&saturator1]() { |
| return saturator1.counter()->bytes() >= bytes_received; |
| }); |
| const QuicTime end_time = simulator.GetClock()->Now(); |
| |
| const QuicBandwidth observed_bandwidth = QuicBandwidth::FromBytesAndTimeDelta( |
| bytes_received, end_time - start_time); |
| const double bandwidth_ratio = |
| static_cast<double>(observed_bandwidth.ToBitsPerSecond()) / |
| bandwidth.ToBitsPerSecond(); |
| EXPECT_NEAR(1, bandwidth_ratio, 0.1); |
| |
| const double normalized_received_packets_for_saturator_2 = |
| static_cast<double>(saturator2.counter()->packets()) / |
| saturator1.counter()->packets(); |
| const double normalized_received_packets_for_saturator_3 = |
| static_cast<double>(saturator3.counter()->packets()) / |
| saturator1.counter()->packets(); |
| EXPECT_NEAR(1, normalized_received_packets_for_saturator_2, 0.1); |
| EXPECT_NEAR(1, normalized_received_packets_for_saturator_3, 0.1); |
| |
| // Since Saturator 1 has its packet arrive first into the switch, switch will |
| // always know how to route traffic to it. |
| EXPECT_EQ(0u, |
| saturator2.counter()->CountPacketsForDestination("Saturator 1")); |
| EXPECT_EQ(0u, |
| saturator3.counter()->CountPacketsForDestination("Saturator 1")); |
| |
| // Packets from the other saturators will be broadcast at least once. |
| EXPECT_EQ(1u, |
| saturator1.counter()->CountPacketsForDestination("Saturator 2")); |
| EXPECT_EQ(1u, |
| saturator3.counter()->CountPacketsForDestination("Saturator 2")); |
| EXPECT_EQ(1u, |
| saturator1.counter()->CountPacketsForDestination("Saturator 3")); |
| EXPECT_EQ(1u, |
| saturator2.counter()->CountPacketsForDestination("Saturator 3")); |
| } |
| |
| // Toggle an alarm on and off at the specified interval. Assumes that alarm is |
| // initially set and unsets it almost immediately after the object is |
| // instantiated. |
| class AlarmToggler : public Actor { |
| public: |
| AlarmToggler(Simulator* simulator, std::string name, QuicAlarm* alarm, |
| QuicTime::Delta interval) |
| : Actor(simulator, name), |
| alarm_(alarm), |
| interval_(interval), |
| deadline_(alarm->deadline()), |
| times_set_(0), |
| times_cancelled_(0) { |
| EXPECT_TRUE(alarm->IsSet()); |
| EXPECT_GE(alarm->deadline(), clock_->Now()); |
| Schedule(clock_->Now()); |
| } |
| |
| void Act() override { |
| if (deadline_ <= clock_->Now()) { |
| return; |
| } |
| |
| if (alarm_->IsSet()) { |
| alarm_->Cancel(); |
| times_cancelled_++; |
| } else { |
| alarm_->Set(deadline_); |
| times_set_++; |
| } |
| |
| Schedule(clock_->Now() + interval_); |
| } |
| |
| inline int times_set() { return times_set_; } |
| inline int times_cancelled() { return times_cancelled_; } |
| |
| private: |
| QuicAlarm* alarm_; |
| QuicTime::Delta interval_; |
| QuicTime deadline_; |
| |
| // Counts the number of times the alarm was set. |
| int times_set_; |
| // Counts the number of times the alarm was cancelled. |
| int times_cancelled_; |
| }; |
| |
| // Counts the number of times an alarm has fired. |
| class CounterDelegate : public QuicAlarm::DelegateWithoutContext { |
| public: |
| explicit CounterDelegate(size_t* counter) : counter_(counter) {} |
| |
| void OnAlarm() override { *counter_ += 1; } |
| |
| private: |
| size_t* counter_; |
| }; |
| |
| // Verifies that the alarms work correctly, even when they are repeatedly |
| // toggled. |
| TEST_F(SimulatorTest, Alarms) { |
| Simulator simulator; |
| QuicAlarmFactory* alarm_factory = simulator.GetAlarmFactory(); |
| |
| size_t fast_alarm_counter = 0; |
| size_t slow_alarm_counter = 0; |
| std::unique_ptr<QuicAlarm> alarm_fast( |
| alarm_factory->CreateAlarm(new CounterDelegate(&fast_alarm_counter))); |
| std::unique_ptr<QuicAlarm> alarm_slow( |
| alarm_factory->CreateAlarm(new CounterDelegate(&slow_alarm_counter))); |
| |
| const QuicTime start_time = simulator.GetClock()->Now(); |
| alarm_fast->Set(start_time + QuicTime::Delta::FromMilliseconds(100)); |
| alarm_slow->Set(start_time + QuicTime::Delta::FromMilliseconds(750)); |
| AlarmToggler toggler(&simulator, "Toggler", alarm_slow.get(), |
| QuicTime::Delta::FromMilliseconds(100)); |
| |
| const QuicTime end_time = |
| start_time + QuicTime::Delta::FromMilliseconds(1000); |
| EXPECT_FALSE(simulator.RunUntil([&simulator, end_time]() { |
| return simulator.GetClock()->Now() >= end_time; |
| })); |
| EXPECT_EQ(1u, slow_alarm_counter); |
| EXPECT_EQ(1u, fast_alarm_counter); |
| |
| EXPECT_EQ(4, toggler.times_set()); |
| EXPECT_EQ(4, toggler.times_cancelled()); |
| } |
| |
| // Verifies that a cancelled alarm is never fired. |
| TEST_F(SimulatorTest, AlarmCancelling) { |
| Simulator simulator; |
| QuicAlarmFactory* alarm_factory = simulator.GetAlarmFactory(); |
| |
| size_t alarm_counter = 0; |
| std::unique_ptr<QuicAlarm> alarm( |
| alarm_factory->CreateAlarm(new CounterDelegate(&alarm_counter))); |
| |
| const QuicTime start_time = simulator.GetClock()->Now(); |
| const QuicTime alarm_at = start_time + QuicTime::Delta::FromMilliseconds(300); |
| const QuicTime end_time = start_time + QuicTime::Delta::FromMilliseconds(400); |
| |
| alarm->Set(alarm_at); |
| alarm->Cancel(); |
| EXPECT_FALSE(alarm->IsSet()); |
| |
| EXPECT_FALSE(simulator.RunUntil([&simulator, end_time]() { |
| return simulator.GetClock()->Now() >= end_time; |
| })); |
| |
| EXPECT_FALSE(alarm->IsSet()); |
| EXPECT_EQ(0u, alarm_counter); |
| } |
| |
| // Verifies that alarms can be scheduled into the past. |
| TEST_F(SimulatorTest, AlarmInPast) { |
| Simulator simulator; |
| QuicAlarmFactory* alarm_factory = simulator.GetAlarmFactory(); |
| |
| size_t alarm_counter = 0; |
| std::unique_ptr<QuicAlarm> alarm( |
| alarm_factory->CreateAlarm(new CounterDelegate(&alarm_counter))); |
| |
| const QuicTime start_time = simulator.GetClock()->Now(); |
| simulator.RunFor(QuicTime::Delta::FromMilliseconds(400)); |
| |
| alarm->Set(start_time); |
| simulator.RunFor(QuicTime::Delta::FromMilliseconds(1)); |
| EXPECT_FALSE(alarm->IsSet()); |
| EXPECT_EQ(1u, alarm_counter); |
| } |
| |
| // Tests Simulator::RunUntilOrTimeout() interface. |
| TEST_F(SimulatorTest, RunUntilOrTimeout) { |
| Simulator simulator; |
| bool simulation_result; |
| |
| // Count the number of seconds since the beginning of the simulation. |
| Counter counter(&simulator, "counter", QuicTime::Delta::FromSeconds(1)); |
| |
| // Ensure that the counter reaches the value of 10 given a 20 second deadline. |
| simulation_result = simulator.RunUntilOrTimeout( |
| [&counter]() { return counter.get_value() == 10; }, |
| QuicTime::Delta::FromSeconds(20)); |
| ASSERT_TRUE(simulation_result); |
| |
| // Ensure that the counter will not reach the value of 100 given that the |
| // starting value is 10 and the deadline is 20 seconds. |
| simulation_result = simulator.RunUntilOrTimeout( |
| [&counter]() { return counter.get_value() == 100; }, |
| QuicTime::Delta::FromSeconds(20)); |
| ASSERT_FALSE(simulation_result); |
| } |
| |
| // Tests Simulator::RunFor() interface. |
| TEST_F(SimulatorTest, RunFor) { |
| Simulator simulator; |
| |
| Counter counter(&simulator, "counter", QuicTime::Delta::FromSeconds(3)); |
| |
| simulator.RunFor(QuicTime::Delta::FromSeconds(100)); |
| |
| EXPECT_EQ(33, counter.get_value()); |
| } |
| |
| class MockPacketFilter : public PacketFilter { |
| public: |
| MockPacketFilter(Simulator* simulator, std::string name, Endpoint* endpoint) |
| : PacketFilter(simulator, name, endpoint) {} |
| MOCK_METHOD(bool, FilterPacket, (const Packet&), (override)); |
| }; |
| |
| // Set up two trivial packet filters, one allowing any packets, and one dropping |
| // all of them. |
| TEST_F(SimulatorTest, PacketFilter) { |
| const QuicBandwidth bandwidth = |
| QuicBandwidth::FromBytesPerSecond(1024 * 1024); |
| const QuicTime::Delta base_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(5); |
| |
| Simulator simulator; |
| LinkSaturator saturator_a(&simulator, "Saturator A", 1000, "Saturator B"); |
| LinkSaturator saturator_b(&simulator, "Saturator B", 1000, "Saturator A"); |
| |
| // Attach packets to the switch to create a delay between the point at which |
| // the packet is generated and the point at which it is filtered. Note that |
| // if the saturators were connected directly, the link would be always |
| // available for the endpoint which has all of its packets dropped, resulting |
| // in saturator looping infinitely. |
| Switch network_switch(&simulator, "Switch", 8, |
| bandwidth * base_propagation_delay * 10); |
| StrictMock<MockPacketFilter> a_to_b_filter(&simulator, "A -> B filter", |
| network_switch.port(1)); |
| StrictMock<MockPacketFilter> b_to_a_filter(&simulator, "B -> A filter", |
| network_switch.port(2)); |
| SymmetricLink link_a(&a_to_b_filter, &saturator_b, bandwidth, |
| base_propagation_delay); |
| SymmetricLink link_b(&b_to_a_filter, &saturator_a, bandwidth, |
| base_propagation_delay); |
| |
| // Allow packets from A to B, but not from B to A. |
| EXPECT_CALL(a_to_b_filter, FilterPacket(_)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(b_to_a_filter, FilterPacket(_)).WillRepeatedly(Return(false)); |
| |
| // Run the simulation for a while, and expect that only B will receive any |
| // packets. |
| simulator.RunFor(QuicTime::Delta::FromSeconds(10)); |
| EXPECT_GE(saturator_b.counter()->packets(), 1u); |
| EXPECT_EQ(saturator_a.counter()->packets(), 0u); |
| } |
| |
| // Set up a traffic policer in one direction that throttles at 25% of link |
| // bandwidth, and put two link saturators at each endpoint. |
| TEST_F(SimulatorTest, TrafficPolicer) { |
| const QuicBandwidth bandwidth = |
| QuicBandwidth::FromBytesPerSecond(1024 * 1024); |
| const QuicTime::Delta base_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(5); |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); |
| |
| Simulator simulator; |
| LinkSaturator saturator1(&simulator, "Saturator 1", 1000, "Saturator 2"); |
| LinkSaturator saturator2(&simulator, "Saturator 2", 1000, "Saturator 1"); |
| Switch network_switch(&simulator, "Switch", 8, |
| bandwidth * base_propagation_delay * 10); |
| |
| static const QuicByteCount initial_burst = 1000 * 10; |
| static const QuicByteCount max_bucket_size = 1000 * 100; |
| static const QuicBandwidth target_bandwidth = bandwidth * 0.25; |
| TrafficPolicer policer(&simulator, "Policer", initial_burst, max_bucket_size, |
| target_bandwidth, network_switch.port(2)); |
| |
| SymmetricLink link1(&saturator1, network_switch.port(1), bandwidth, |
| base_propagation_delay); |
| SymmetricLink link2(&saturator2, &policer, bandwidth, base_propagation_delay); |
| |
| // Ensure the initial burst passes without being dropped at all. |
| bool simulator_result = simulator.RunUntilOrTimeout( |
| [&saturator1]() { |
| return saturator1.bytes_transmitted() == initial_burst; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| saturator1.Pause(); |
| simulator_result = simulator.RunUntilOrTimeout( |
| [&saturator2]() { |
| return saturator2.counter()->bytes() == initial_burst; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| saturator1.Resume(); |
| |
| // Run for some time so that the initial burst is not visible. |
| const QuicTime::Delta simulation_time = QuicTime::Delta::FromSeconds(10); |
| simulator.RunFor(simulation_time); |
| |
| // Ensure we've transmitted the amount of data we expected. |
| for (auto* saturator : {&saturator1, &saturator2}) { |
| EXPECT_APPROX_EQ(bandwidth * simulation_time, |
| saturator->bytes_transmitted(), 0.01f); |
| } |
| |
| // Check that only one direction is throttled. |
| EXPECT_APPROX_EQ(saturator1.bytes_transmitted() / 4, |
| saturator2.counter()->bytes(), 0.1f); |
| EXPECT_APPROX_EQ(saturator2.bytes_transmitted(), |
| saturator1.counter()->bytes(), 0.1f); |
| } |
| |
| // Ensure that a larger burst is allowed when the policed saturator exits |
| // quiescence. |
| TEST_F(SimulatorTest, TrafficPolicerBurst) { |
| const QuicBandwidth bandwidth = |
| QuicBandwidth::FromBytesPerSecond(1024 * 1024); |
| const QuicTime::Delta base_propagation_delay = |
| QuicTime::Delta::FromMilliseconds(5); |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); |
| |
| Simulator simulator; |
| LinkSaturator saturator1(&simulator, "Saturator 1", 1000, "Saturator 2"); |
| LinkSaturator saturator2(&simulator, "Saturator 2", 1000, "Saturator 1"); |
| Switch network_switch(&simulator, "Switch", 8, |
| bandwidth * base_propagation_delay * 10); |
| |
| const QuicByteCount initial_burst = 1000 * 10; |
| const QuicByteCount max_bucket_size = 1000 * 100; |
| const QuicBandwidth target_bandwidth = bandwidth * 0.25; |
| TrafficPolicer policer(&simulator, "Policer", initial_burst, max_bucket_size, |
| target_bandwidth, network_switch.port(2)); |
| |
| SymmetricLink link1(&saturator1, network_switch.port(1), bandwidth, |
| base_propagation_delay); |
| SymmetricLink link2(&saturator2, &policer, bandwidth, base_propagation_delay); |
| |
| // Ensure at least one packet is sent on each side. |
| bool simulator_result = simulator.RunUntilOrTimeout( |
| [&saturator1, &saturator2]() { |
| return saturator1.packets_transmitted() > 0 && |
| saturator2.packets_transmitted() > 0; |
| }, |
| timeout); |
| ASSERT_TRUE(simulator_result); |
| |
| // Wait until the bucket fills up. |
| saturator1.Pause(); |
| saturator2.Pause(); |
| simulator.RunFor(1.5f * target_bandwidth.TransferTime(max_bucket_size)); |
| |
| // Send a burst. |
| saturator1.Resume(); |
| simulator.RunFor(bandwidth.TransferTime(max_bucket_size)); |
| saturator1.Pause(); |
| simulator.RunFor(2 * base_propagation_delay); |
| |
| // Expect the burst to pass without losses. |
| EXPECT_APPROX_EQ(saturator1.bytes_transmitted(), |
| saturator2.counter()->bytes(), 0.1f); |
| |
| // Expect subsequent traffic to be policed. |
| saturator1.Resume(); |
| simulator.RunFor(QuicTime::Delta::FromSeconds(10)); |
| EXPECT_APPROX_EQ(saturator1.bytes_transmitted() / 4, |
| saturator2.counter()->bytes(), 0.1f); |
| } |
| |
| // Test that the packet aggregation support in queues work. |
| TEST_F(SimulatorTest, PacketAggregation) { |
| // Model network where the delays are dominated by transfer delay. |
| const QuicBandwidth bandwidth = QuicBandwidth::FromBytesPerSecond(1000); |
| const QuicTime::Delta base_propagation_delay = |
| QuicTime::Delta::FromMicroseconds(1); |
| const QuicByteCount aggregation_threshold = 1000; |
| const QuicTime::Delta aggregation_timeout = QuicTime::Delta::FromSeconds(30); |
| |
| Simulator simulator; |
| LinkSaturator saturator1(&simulator, "Saturator 1", 10, "Saturator 2"); |
| LinkSaturator saturator2(&simulator, "Saturator 2", 10, "Saturator 1"); |
| Switch network_switch(&simulator, "Switch", 8, 10 * aggregation_threshold); |
| |
| // Make links with asymmetric propagation delay so that Saturator 2 only |
| // receives packets addressed to it. |
| SymmetricLink link1(&saturator1, network_switch.port(1), bandwidth, |
| base_propagation_delay); |
| SymmetricLink link2(&saturator2, network_switch.port(2), bandwidth, |
| 2 * base_propagation_delay); |
| |
| // Enable aggregation in 1 -> 2 direction. |
| Queue* queue = network_switch.port_queue(2); |
| queue->EnableAggregation(aggregation_threshold, aggregation_timeout); |
| |
| // Enable aggregation in 2 -> 1 direction in a way that all packets are larger |
| // than the threshold, so that aggregation is effectively a no-op. |
| network_switch.port_queue(1)->EnableAggregation(5, aggregation_timeout); |
| |
| // Fill up the aggregation buffer up to 90% (900 bytes). |
| simulator.RunFor(0.9 * bandwidth.TransferTime(aggregation_threshold)); |
| EXPECT_EQ(0u, saturator2.counter()->bytes()); |
| |
| // Stop sending, ensure that given a timespan much shorter than timeout, the |
| // packets remain in the queue. |
| saturator1.Pause(); |
| saturator2.Pause(); |
| simulator.RunFor(QuicTime::Delta::FromSeconds(10)); |
| EXPECT_EQ(0u, saturator2.counter()->bytes()); |
| EXPECT_EQ(900u, queue->bytes_queued()); |
| |
| // Ensure that all packets have reached the saturator not affected by |
| // aggregation. Here, 10 extra bytes account for a misrouted packet in the |
| // beginning. |
| EXPECT_EQ(910u, saturator1.counter()->bytes()); |
| |
| // Send 500 more bytes. Since the aggregation threshold is 1000 bytes, and |
| // queue already has 900 bytes, 1000 bytes will be send and 400 will be in the |
| // queue. |
| saturator1.Resume(); |
| simulator.RunFor(0.5 * bandwidth.TransferTime(aggregation_threshold)); |
| saturator1.Pause(); |
| simulator.RunFor(QuicTime::Delta::FromSeconds(10)); |
| EXPECT_EQ(1000u, saturator2.counter()->bytes()); |
| EXPECT_EQ(400u, queue->bytes_queued()); |
| |
| // Actually time out, and cause all of the data to be received. |
| simulator.RunFor(aggregation_timeout); |
| EXPECT_EQ(1400u, saturator2.counter()->bytes()); |
| EXPECT_EQ(0u, queue->bytes_queued()); |
| |
| // Run saturator for a longer time, to ensure that the logic to cancel and |
| // reset alarms works correctly. |
| saturator1.Resume(); |
| simulator.RunFor(5.5 * bandwidth.TransferTime(aggregation_threshold)); |
| saturator1.Pause(); |
| simulator.RunFor(QuicTime::Delta::FromSeconds(10)); |
| EXPECT_EQ(6400u, saturator2.counter()->bytes()); |
| EXPECT_EQ(500u, queue->bytes_queued()); |
| |
| // Time out again. |
| simulator.RunFor(aggregation_timeout); |
| EXPECT_EQ(6900u, saturator2.counter()->bytes()); |
| EXPECT_EQ(0u, queue->bytes_queued()); |
| } |
| |
| } // namespace simulator |
| } // namespace quic |