diff options
Diffstat (limited to 'third_party/libwebrtc/call/simulated_network.cc')
-rw-r--r-- | third_party/libwebrtc/call/simulated_network.cc | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/third_party/libwebrtc/call/simulated_network.cc b/third_party/libwebrtc/call/simulated_network.cc new file mode 100644 index 0000000000..8f9d76dfe3 --- /dev/null +++ b/third_party/libwebrtc/call/simulated_network.cc @@ -0,0 +1,276 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/simulated_network.h" + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <utility> + +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +// Calculate the time (in microseconds) that takes to send N `bits` on a +// network with link capacity equal to `capacity_kbps` starting at time +// `start_time_us`. +int64_t CalculateArrivalTimeUs(int64_t start_time_us, + int64_t bits, + int capacity_kbps) { + // If capacity is 0, the link capacity is assumed to be infinite. + if (capacity_kbps == 0) { + return start_time_us; + } + // Adding `capacity - 1` to the numerator rounds the extra delay caused by + // 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 + // 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 + // being us and 10^3 is due to the rate unit being kbps. + return start_time_us + ((1000 * bits + capacity_kbps - 1) / capacity_kbps); +} + +} // namespace + +SimulatedNetwork::SimulatedNetwork(Config config, uint64_t random_seed) + : random_(random_seed), + bursting_(false), + last_enqueue_time_us_(0), + last_capacity_link_exit_time_(0) { + SetConfig(config); +} + +SimulatedNetwork::~SimulatedNetwork() = default; + +void SimulatedNetwork::SetConfig(const Config& config) { + MutexLock lock(&config_lock_); + config_state_.config = config; // Shallow copy of the struct. + double prob_loss = config.loss_percent / 100.0; + if (config_state_.config.avg_burst_loss_length == -1) { + // Uniform loss + config_state_.prob_loss_bursting = prob_loss; + config_state_.prob_start_bursting = prob_loss; + } else { + // Lose packets according to a gilbert-elliot model. + int avg_burst_loss_length = config.avg_burst_loss_length; + int min_avg_burst_loss_length = std::ceil(prob_loss / (1 - prob_loss)); + + RTC_CHECK_GT(avg_burst_loss_length, min_avg_burst_loss_length) + << "For a total packet loss of " << config.loss_percent + << "%% then" + " avg_burst_loss_length must be " + << min_avg_burst_loss_length + 1 << " or higher."; + + config_state_.prob_loss_bursting = (1.0 - 1.0 / avg_burst_loss_length); + config_state_.prob_start_bursting = + prob_loss / (1 - prob_loss) / avg_burst_loss_length; + } +} + +void SimulatedNetwork::UpdateConfig( + std::function<void(BuiltInNetworkBehaviorConfig*)> config_modifier) { + MutexLock lock(&config_lock_); + config_modifier(&config_state_.config); +} + +void SimulatedNetwork::PauseTransmissionUntil(int64_t until_us) { + MutexLock lock(&config_lock_); + config_state_.pause_transmission_until_us = until_us; +} + +bool SimulatedNetwork::EnqueuePacket(PacketInFlightInfo packet) { + RTC_DCHECK_RUNS_SERIALIZED(&process_checker_); + + // Check that old packets don't get enqueued, the SimulatedNetwork expect that + // the packets' send time is monotonically increasing. The tolerance for + // non-monotonic enqueue events is 0.5 ms because on multi core systems + // clock_gettime(CLOCK_MONOTONIC) can show non-monotonic behaviour between + // theads running on different cores. + // TODO(bugs.webrtc.org/14525): Open a bug on this with the goal to re-enable + // the DCHECK. + // At the moment, we see more than 130ms between non-monotonic events, which + // is more than expected. + // RTC_DCHECK_GE(packet.send_time_us - last_enqueue_time_us_, -2000); + + ConfigState state = GetConfigState(); + + // If the network config requires packet overhead, let's apply it as early as + // possible. + packet.size += state.config.packet_overhead; + + // If `queue_length_packets` is 0, the queue size is infinite. + if (state.config.queue_length_packets > 0 && + capacity_link_.size() >= state.config.queue_length_packets) { + // Too many packet on the link, drop this one. + return false; + } + + // If the packet has been sent before the previous packet in the network left + // the capacity queue, let's ensure the new packet will start its trip in the + // network after the last bit of the previous packet has left it. + int64_t packet_send_time_us = packet.send_time_us; + if (!capacity_link_.empty()) { + packet_send_time_us = + std::max(packet_send_time_us, capacity_link_.back().arrival_time_us); + } + capacity_link_.push({.packet = packet, + .arrival_time_us = CalculateArrivalTimeUs( + packet_send_time_us, packet.size * 8, + state.config.link_capacity_kbps)}); + + // Only update `next_process_time_us_` if not already set (if set, there is no + // way that a new packet will make the `next_process_time_us_` change). + if (!next_process_time_us_) { + RTC_DCHECK_EQ(capacity_link_.size(), 1); + next_process_time_us_ = capacity_link_.front().arrival_time_us; + } + + last_enqueue_time_us_ = packet.send_time_us; + return true; +} + +absl::optional<int64_t> SimulatedNetwork::NextDeliveryTimeUs() const { + RTC_DCHECK_RUNS_SERIALIZED(&process_checker_); + return next_process_time_us_; +} + +void SimulatedNetwork::UpdateCapacityQueue(ConfigState state, + int64_t time_now_us) { + // If there is at least one packet in the `capacity_link_`, let's update its + // arrival time to take into account changes in the network configuration + // since the last call to UpdateCapacityQueue. + if (!capacity_link_.empty()) { + capacity_link_.front().arrival_time_us = CalculateArrivalTimeUs( + std::max(capacity_link_.front().packet.send_time_us, + last_capacity_link_exit_time_), + capacity_link_.front().packet.size * 8, + state.config.link_capacity_kbps); + } + + // The capacity link is empty or the first packet is not expected to exit yet. + if (capacity_link_.empty() || + time_now_us < capacity_link_.front().arrival_time_us) { + return; + } + bool reorder_packets = false; + + do { + // Time to get this packet (the original or just updated arrival_time_us is + // smaller or equal to time_now_us). + PacketInfo packet = capacity_link_.front(); + capacity_link_.pop(); + + // If the network is paused, the pause will be implemented as an extra delay + // to be spent in the `delay_link_` queue. + if (state.pause_transmission_until_us > packet.arrival_time_us) { + packet.arrival_time_us = state.pause_transmission_until_us; + } + + // 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 + // next packet in the `capacity_link_` queue can start transmitting. + last_capacity_link_exit_time_ = packet.arrival_time_us; + + // Drop packets at an average rate of `state.config.loss_percent` with + // and average loss burst length of `state.config.avg_burst_loss_length`. + if ((bursting_ && random_.Rand<double>() < state.prob_loss_bursting) || + (!bursting_ && random_.Rand<double>() < state.prob_start_bursting)) { + bursting_ = true; + packet.arrival_time_us = PacketDeliveryInfo::kNotReceived; + } else { + // If packets are not dropped, apply extra delay as configured. + bursting_ = false; + int64_t arrival_time_jitter_us = std::max( + random_.Gaussian(state.config.queue_delay_ms * 1000, + state.config.delay_standard_deviation_ms * 1000), + 0.0); + + // If reordering is not allowed then adjust arrival_time_jitter + // to make sure all packets are sent in order. + int64_t last_arrival_time_us = + delay_link_.empty() ? -1 : delay_link_.back().arrival_time_us; + if (!state.config.allow_reordering && !delay_link_.empty() && + packet.arrival_time_us + arrival_time_jitter_us < + last_arrival_time_us) { + arrival_time_jitter_us = last_arrival_time_us - packet.arrival_time_us; + } + packet.arrival_time_us += arrival_time_jitter_us; + + // Optimization: Schedule a reorder only when a packet will exit before + // the one in front. + if (last_arrival_time_us > packet.arrival_time_us) { + reorder_packets = true; + } + } + delay_link_.emplace_back(packet); + + // If there are no packets in the queue, there is nothing else to do. + if (capacity_link_.empty()) { + break; + } + // 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 + // have been changed since it was enqueued). + int64_t next_start = std::max(last_capacity_link_exit_time_, + capacity_link_.front().packet.send_time_us); + capacity_link_.front().arrival_time_us = CalculateArrivalTimeUs( + next_start, capacity_link_.front().packet.size * 8, + state.config.link_capacity_kbps); + // 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); + + if (state.config.allow_reordering && reorder_packets) { + // 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 + // be delivered out of order. + std::stable_sort(delay_link_.begin(), delay_link_.end(), + [](const PacketInfo& p1, const PacketInfo& p2) { + return p1.arrival_time_us < p2.arrival_time_us; + }); + } +} + +SimulatedNetwork::ConfigState SimulatedNetwork::GetConfigState() const { + MutexLock lock(&config_lock_); + return config_state_; +} + +std::vector<PacketDeliveryInfo> SimulatedNetwork::DequeueDeliverablePackets( + int64_t receive_time_us) { + RTC_DCHECK_RUNS_SERIALIZED(&process_checker_); + + UpdateCapacityQueue(GetConfigState(), receive_time_us); + std::vector<PacketDeliveryInfo> packets_to_deliver; + + // Check the extra delay queue. + while (!delay_link_.empty() && + receive_time_us >= delay_link_.front().arrival_time_us) { + PacketInfo packet_info = delay_link_.front(); + packets_to_deliver.emplace_back( + PacketDeliveryInfo(packet_info.packet, packet_info.arrival_time_us)); + delay_link_.pop_front(); + } + + if (!delay_link_.empty()) { + next_process_time_us_ = delay_link_.front().arrival_time_us; + } else if (!capacity_link_.empty()) { + next_process_time_us_ = capacity_link_.front().arrival_time_us; + } else { + next_process_time_us_.reset(); + } + return packets_to_deliver; +} + +} // namespace webrtc |