/* * 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.
*/
// Calculate the time that it takes to send N `bits` on a // network with link capacity equal to `capacity_kbps` starting at time // `start_time`.
Timestamp CalculateArrivalTime(Timestamp start_time,
int64_t bits,
DataRate capacity) { if (capacity.IsInfinite()) { return start_time;
} if (capacity.IsZero()) { return Timestamp::PlusInfinity();
}
// 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 + TimeDelta::Micros((1000 * bits + capacity.kbps() - 1) /
capacity.kbps());
}
void SimulatedNetwork::SetConfig(const Config& config) {
MutexLock lock(&config_lock_);
config_state_.config = config; // Shallow copy of the struct.
UpdateLegacyConfiguration(config_state_.config);
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.";
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_();
}
}
// 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. returnfalse;
}
// Note that arrival time will be updated when previous packets are dequeued // from the capacity link. // A packet can not enter the narrow section before the last packet has exit.
Timestamp enqueue_time = Timestamp::Micros(packet.send_time_us);
Timestamp arrival_time =
capacity_link_.empty()
? CalculateArrivalTime(
std::max(enqueue_time, last_capacity_link_exit_time_),
packet.size * 8, state.config.link_capacity)
: Timestamp::PlusInfinity();
capacity_link_.push(
{.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_` if not already set. Otherwise, // next_process_time_ is calculated when a packet is dequeued. Note that this // means that the newly enqueued 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() && arrival_time.IsFinite()) {
RTC_DCHECK_EQ(capacity_link_.size(), 1);
next_process_time_ = arrival_time;
}
void SimulatedNetwork::UpdateCapacityQueue(ConfigState state,
Timestamp time_now) { // Only the first packet in capacity_link_ have a calculated arrival time // (when packet leave the narrow section), and time when it entered the narrow // 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()) {
capacity_link_.front().last_update_time = std::max(
capacity_link_.front().last_update_time, last_capacity_link_exit_time_);
capacity_link_.front().arrival_time = CalculateArrivalTime(
capacity_link_.front().last_update_time,
capacity_link_.front().bits_left_to_send, state.config.link_capacity);
}
// The capacity link is empty or the first packet is not expected to exit yet. if (capacity_link_.empty() ||
time_now < capacity_link_.front().arrival_time) { return;
} bool reorder_packets = false;
do { // Time to get this packet (the original or just updated arrival_time is // smaller or equal to time_now_us).
PacketInfo packet = capacity_link_.front();
RTC_DCHECK(packet.arrival_time.IsFinite());
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 =
Timestamp::Micros(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;
// 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 = Timestamp::MinusInfinity();
} else { // If packets are not dropped, apply extra delay as configured.
bursting_ = false;
TimeDelta arrival_time_jitter = TimeDelta::Micros(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.
Timestamp last_arrival_time = delay_link_.empty()
? Timestamp::MinusInfinity()
: delay_link_.back().arrival_time; if (!state.config.allow_reordering && !delay_link_.empty() &&
packet.arrival_time + arrival_time_jitter < last_arrival_time) {
arrival_time_jitter = last_arrival_time - packet.arrival_time;
}
packet.arrival_time += arrival_time_jitter;
// Optimization: Schedule a reorder only when a packet will exit before // the one in front. if (last_arrival_time > packet.arrival_time) {
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 based on the latest config (which might // have been changed since it was enqueued).
Timestamp next_start = std::max(last_capacity_link_exit_time_,
capacity_link_.front().last_update_time);
capacity_link_.front().arrival_time =
CalculateArrivalTime(next_start, capacity_link_.front().packet.size * 8,
state.config.link_capacity); // And if the next packet in the queue needs to exit, let's dequeue it.
} while (capacity_link_.front().arrival_time <= time_now);
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 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 < p2.arrival_time;
});
}
}
// Check the extra delay queue. while (!delay_link_.empty() &&
receive_time >= delay_link_.front().arrival_time) {
PacketInfo packet_info = delay_link_.front();
packets_to_deliver.emplace_back(PacketDeliveryInfo(
packet_info.packet, packet_info.arrival_time.IsFinite()
? packet_info.arrival_time.us()
: PacketDeliveryInfo::kNotReceived));
delay_link_.pop_front();
} // There is no need to invoke `next_process_time_changed_callback_` here since // it is expected that the user of NetworkBehaviorInterface calls // NextDeliveryTimeUs after DequeueDeliverablePackets. See // NetworkBehaviorInterface.
UpdateNextProcessTime(); return packets_to_deliver;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.