| // Copyright (c) 2013 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 "net/quic/congestion_control/inter_arrival_sender.h" |
| |
| namespace net { |
| |
| namespace { |
| const int64 kProbeBitrateKBytesPerSecond = 1200; // 9.6 Mbit/s |
| const float kPacketLossBitrateReduction = 0.7f; |
| const float kUncertainSafetyMargin = 0.7f; |
| const float kMaxBitrateReduction = 0.9f; |
| const float kMinBitrateReduction = 0.05f; |
| const uint64 kMinBitrateKbit = 10; |
| const int kInitialRttMs = 60; // At a typical RTT 60 ms. |
| const float kAlpha = 0.125f; |
| const float kOneMinusAlpha = 1 - kAlpha; |
| |
| static const int kBitrateSmoothingPeriodMs = 1000; |
| static const int kMinBitrateSmoothingPeriodMs = 500; |
| |
| } // namespace |
| |
| InterArrivalSender::InterArrivalSender(const QuicClock* clock) |
| : probing_(true), |
| max_segment_size_(kDefaultMaxPacketSize), |
| current_bandwidth_(QuicBandwidth::Zero()), |
| smoothed_rtt_(QuicTime::Delta::Zero()), |
| channel_estimator_(new ChannelEstimator()), |
| bitrate_ramp_up_(new InterArrivalBitrateRampUp(clock)), |
| overuse_detector_(new InterArrivalOveruseDetector()), |
| probe_(new InterArrivalProbe(max_segment_size_)), |
| state_machine_(new InterArrivalStateMachine(clock)), |
| paced_sender_(new PacedSender(QuicBandwidth::FromKBytesPerSecond( |
| kProbeBitrateKBytesPerSecond), max_segment_size_)), |
| accumulated_number_of_lost_packets_(0), |
| bandwidth_usage_state_(kBandwidthSteady), |
| back_down_time_(QuicTime::Zero()), |
| back_down_bandwidth_(QuicBandwidth::Zero()), |
| back_down_congestion_delay_(QuicTime::Delta::Zero()) { |
| } |
| |
| InterArrivalSender::~InterArrivalSender() { |
| } |
| |
| void InterArrivalSender::SetFromConfig(const QuicConfig& config, |
| bool is_server) { |
| max_segment_size_ = config.server_max_packet_size(); |
| paced_sender_->set_max_segment_size(max_segment_size_); |
| probe_->set_max_segment_size(max_segment_size_); |
| } |
| |
| // TODO(pwestin): this is really inefficient (4% CPU on the GFE loadtest). |
| // static |
| QuicBandwidth InterArrivalSender::CalculateSentBandwidth( |
| const SendAlgorithmInterface::SentPacketsMap& sent_packets_map, |
| QuicTime feedback_receive_time) { |
| const QuicTime::Delta kBitrateSmoothingPeriod = |
| QuicTime::Delta::FromMilliseconds(kBitrateSmoothingPeriodMs); |
| const QuicTime::Delta kMinBitrateSmoothingPeriod = |
| QuicTime::Delta::FromMilliseconds(kMinBitrateSmoothingPeriodMs); |
| |
| QuicByteCount sum_bytes_sent = 0; |
| |
| // Sum packet from new until they are kBitrateSmoothingPeriod old. |
| SendAlgorithmInterface::SentPacketsMap::const_reverse_iterator history_rit = |
| sent_packets_map.rbegin(); |
| |
| QuicTime::Delta max_diff = QuicTime::Delta::Zero(); |
| for (; history_rit != sent_packets_map.rend(); ++history_rit) { |
| QuicTime::Delta diff = |
| feedback_receive_time.Subtract(history_rit->second->SendTimestamp()); |
| if (diff > kBitrateSmoothingPeriod) { |
| break; |
| } |
| sum_bytes_sent += history_rit->second->BytesSent(); |
| max_diff = diff; |
| } |
| if (max_diff < kMinBitrateSmoothingPeriod) { |
| // No estimate. |
| return QuicBandwidth::Zero(); |
| } |
| return QuicBandwidth::FromBytesAndTimeDelta(sum_bytes_sent, max_diff); |
| } |
| |
| void InterArrivalSender::OnIncomingQuicCongestionFeedbackFrame( |
| const QuicCongestionFeedbackFrame& feedback, |
| QuicTime feedback_receive_time, |
| const SentPacketsMap& sent_packets) { |
| DCHECK(feedback.type == kInterArrival); |
| |
| if (feedback.type != kInterArrival) { |
| return; |
| } |
| |
| QuicBandwidth sent_bandwidth = CalculateSentBandwidth(sent_packets, |
| feedback_receive_time); |
| |
| TimeMap::const_iterator received_it; |
| for (received_it = feedback.inter_arrival.received_packet_times.begin(); |
| received_it != feedback.inter_arrival.received_packet_times.end(); |
| ++received_it) { |
| QuicPacketSequenceNumber sequence_number = received_it->first; |
| |
| SentPacketsMap::const_iterator sent_it = sent_packets.find(sequence_number); |
| if (sent_it == sent_packets.end()) { |
| // Too old data; ignore and move forward. |
| DLOG(INFO) << "Too old feedback move forward, sequence_number:" |
| << sequence_number; |
| continue; |
| } |
| QuicTime time_received = received_it->second; |
| QuicTime time_sent = sent_it->second->SendTimestamp(); |
| QuicByteCount bytes_sent = sent_it->second->BytesSent(); |
| |
| channel_estimator_->OnAcknowledgedPacket( |
| sequence_number, bytes_sent, time_sent, time_received); |
| if (probing_) { |
| probe_->OnIncomingFeedback( |
| sequence_number, bytes_sent, time_sent, time_received); |
| } else { |
| bool last_of_send_time = false; |
| SentPacketsMap::const_iterator next_sent_it = ++sent_it; |
| if (next_sent_it == sent_packets.end()) { |
| // No more sent packets; hence this must be the last. |
| last_of_send_time = true; |
| } else { |
| if (time_sent != next_sent_it->second->SendTimestamp()) { |
| // Next sent packet have a different send time. |
| last_of_send_time = true; |
| } |
| } |
| overuse_detector_->OnAcknowledgedPacket( |
| sequence_number, time_sent, last_of_send_time, time_received); |
| } |
| } |
| if (probing_) { |
| probing_ = ProbingPhase(feedback_receive_time); |
| return; |
| } |
| |
| bool packet_loss_event = false; |
| if (accumulated_number_of_lost_packets_ != |
| feedback.inter_arrival.accumulated_number_of_lost_packets) { |
| accumulated_number_of_lost_packets_ = |
| feedback.inter_arrival.accumulated_number_of_lost_packets; |
| packet_loss_event = true; |
| } |
| InterArrivalState state = state_machine_->GetInterArrivalState(); |
| |
| if (state == kInterArrivalStatePacketLoss || |
| state == kInterArrivalStateCompetingTcpFLow) { |
| if (packet_loss_event) { |
| if (!state_machine_->PacketLossEvent()) { |
| // Less than one RTT since last PacketLossEvent. |
| return; |
| } |
| EstimateBandwidthAfterLossEvent(feedback_receive_time); |
| } else { |
| EstimateNewBandwidth(feedback_receive_time, sent_bandwidth); |
| } |
| return; |
| } |
| EstimateDelayBandwidth(feedback_receive_time, sent_bandwidth); |
| } |
| |
| bool InterArrivalSender::ProbingPhase(QuicTime feedback_receive_time) { |
| QuicBandwidth available_channel_estimate = QuicBandwidth::Zero(); |
| if (!probe_->GetEstimate(&available_channel_estimate)) { |
| // Continue probing phase. |
| return true; |
| } |
| QuicBandwidth channel_estimate = QuicBandwidth::Zero(); |
| ChannelEstimateState channel_estimator_state = |
| channel_estimator_->GetChannelEstimate(&channel_estimate); |
| |
| QuicBandwidth new_rate = |
| available_channel_estimate.Scale(kUncertainSafetyMargin); |
| |
| switch (channel_estimator_state) { |
| case kChannelEstimateUnknown: |
| channel_estimate = available_channel_estimate; |
| break; |
| case kChannelEstimateUncertain: |
| channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin); |
| break; |
| case kChannelEstimateGood: |
| // Do nothing. |
| break; |
| } |
| new_rate = std::max(new_rate, |
| QuicBandwidth::FromKBitsPerSecond(kMinBitrateKbit)); |
| |
| bitrate_ramp_up_->Reset(new_rate, available_channel_estimate, |
| channel_estimate); |
| |
| current_bandwidth_ = new_rate; |
| paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, new_rate); |
| DLOG(INFO) << "Probe result; new rate:" |
| << new_rate.ToKBitsPerSecond() << " Kbits/s " |
| << " available estimate:" |
| << available_channel_estimate.ToKBitsPerSecond() << " Kbits/s " |
| << " channel estimate:" |
| << channel_estimate.ToKBitsPerSecond() << " Kbits/s "; |
| return false; |
| } |
| |
| void InterArrivalSender::OnIncomingAck( |
| QuicPacketSequenceNumber /*acked_sequence_number*/, |
| QuicByteCount acked_bytes, |
| QuicTime::Delta rtt) { |
| // RTT can't be negative. |
| DCHECK_LE(0, rtt.ToMicroseconds()); |
| |
| if (probing_) { |
| probe_->OnAcknowledgedPacket(acked_bytes); |
| } |
| |
| if (rtt.IsInfinite()) { |
| return; |
| } |
| |
| if (smoothed_rtt_.IsZero()) { |
| smoothed_rtt_ = rtt; |
| } else { |
| smoothed_rtt_ = QuicTime::Delta::FromMicroseconds( |
| kOneMinusAlpha * smoothed_rtt_.ToMicroseconds() + |
| kAlpha * rtt.ToMicroseconds()); |
| } |
| state_machine_->set_rtt(smoothed_rtt_); |
| } |
| |
| void InterArrivalSender::OnIncomingLoss(QuicTime ack_receive_time) { |
| // Packet loss was reported. |
| if (!probing_) { |
| if (!state_machine_->PacketLossEvent()) { |
| // Less than one RTT since last PacketLossEvent. |
| return; |
| } |
| // Calculate new pace rate. |
| EstimateBandwidthAfterLossEvent(ack_receive_time); |
| } |
| } |
| |
| bool InterArrivalSender::OnPacketSent( |
| QuicTime sent_time, |
| QuicPacketSequenceNumber sequence_number, |
| QuicByteCount bytes, |
| TransmissionType /*transmission_type*/, |
| HasRetransmittableData /*has_retransmittable_data*/) { |
| if (probing_) { |
| probe_->OnPacketSent(bytes); |
| } |
| paced_sender_->OnPacketSent(sent_time, bytes); |
| return true; |
| } |
| |
| void InterArrivalSender::OnPacketAbandoned( |
| QuicPacketSequenceNumber /*sequence_number*/, |
| QuicByteCount abandoned_bytes) { |
| // TODO(pwestin): use for out outer_congestion_window_ logic. |
| if (probing_) { |
| probe_->OnAcknowledgedPacket(abandoned_bytes); |
| } |
| } |
| |
| QuicTime::Delta InterArrivalSender::TimeUntilSend( |
| QuicTime now, |
| TransmissionType /*transmission_type*/, |
| HasRetransmittableData has_retransmittable_data, |
| IsHandshake /*handshake*/) { |
| // TODO(pwestin): implement outer_congestion_window_ logic. |
| QuicTime::Delta outer_window = QuicTime::Delta::Zero(); |
| |
| if (probing_) { |
| if (has_retransmittable_data == HAS_RETRANSMITTABLE_DATA && |
| probe_->GetAvailableCongestionWindow() == 0) { |
| outer_window = QuicTime::Delta::Infinite(); |
| } |
| } |
| return paced_sender_->TimeUntilSend(now, outer_window); |
| } |
| |
| void InterArrivalSender::EstimateDelayBandwidth(QuicTime feedback_receive_time, |
| QuicBandwidth sent_bandwidth) { |
| QuicTime::Delta estimated_congestion_delay = QuicTime::Delta::Zero(); |
| BandwidthUsage new_bandwidth_usage_state = |
| overuse_detector_->GetState(&estimated_congestion_delay); |
| |
| switch (new_bandwidth_usage_state) { |
| case kBandwidthDraining: |
| case kBandwidthUnderUsing: |
| // Hold our current bitrate. |
| break; |
| case kBandwidthOverUsing: |
| if (!state_machine_->IncreasingDelayEvent()) { |
| // Less than one RTT since last IncreasingDelayEvent. |
| return; |
| } |
| EstimateBandwidthAfterDelayEvent(feedback_receive_time, |
| estimated_congestion_delay); |
| break; |
| case kBandwidthSteady: |
| // Calculate new pace rate. |
| if (bandwidth_usage_state_ == kBandwidthDraining || |
| bandwidth_usage_state_ == kBandwidthOverUsing) { |
| EstimateNewBandwidthAfterDraining(feedback_receive_time, |
| estimated_congestion_delay); |
| } else { |
| EstimateNewBandwidth(feedback_receive_time, sent_bandwidth); |
| } |
| break; |
| } |
| bandwidth_usage_state_ = new_bandwidth_usage_state; |
| } |
| |
| QuicBandwidth InterArrivalSender::BandwidthEstimate() { |
| return current_bandwidth_; |
| } |
| |
| QuicTime::Delta InterArrivalSender::SmoothedRtt() { |
| if (smoothed_rtt_.IsZero()) { |
| return QuicTime::Delta::FromMilliseconds(kInitialRttMs); |
| } |
| return smoothed_rtt_; |
| } |
| |
| QuicTime::Delta InterArrivalSender::RetransmissionDelay() { |
| // TODO(pwestin): Calculate and return retransmission delay. |
| // Use 2 * the smoothed RTT for now. |
| return smoothed_rtt_.Add(smoothed_rtt_); |
| } |
| |
| QuicByteCount InterArrivalSender::GetCongestionWindow() { |
| return 0; |
| } |
| |
| void InterArrivalSender::SetCongestionWindow(QuicByteCount window) { |
| } |
| |
| void InterArrivalSender::EstimateNewBandwidth(QuicTime feedback_receive_time, |
| QuicBandwidth sent_bandwidth) { |
| QuicBandwidth new_bandwidth = bitrate_ramp_up_->GetNewBitrate(sent_bandwidth); |
| if (current_bandwidth_ == new_bandwidth) { |
| return; |
| } |
| current_bandwidth_ = new_bandwidth; |
| state_machine_->IncreaseBitrateDecision(); |
| |
| QuicBandwidth channel_estimate = QuicBandwidth::Zero(); |
| ChannelEstimateState channel_estimator_state = |
| channel_estimator_->GetChannelEstimate(&channel_estimate); |
| |
| if (channel_estimator_state == kChannelEstimateGood) { |
| bitrate_ramp_up_->UpdateChannelEstimate(channel_estimate); |
| } |
| paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, |
| current_bandwidth_); |
| DLOG(INFO) << "New bandwidth estimate in steady state:" |
| << current_bandwidth_.ToKBitsPerSecond() |
| << " Kbits/s"; |
| } |
| |
| // Did we drain the network buffers in our expected pace? |
| void InterArrivalSender::EstimateNewBandwidthAfterDraining( |
| QuicTime feedback_receive_time, |
| QuicTime::Delta estimated_congestion_delay) { |
| if (current_bandwidth_ > back_down_bandwidth_) { |
| // Do nothing, our current bandwidth is higher than our bandwidth at the |
| // previous back down. |
| DLOG(INFO) << "Current bandwidth estimate is higher than before draining"; |
| return; |
| } |
| if (estimated_congestion_delay >= back_down_congestion_delay_) { |
| // Do nothing, our estimated delay have increased. |
| DLOG(INFO) << "Current delay estimate is higher than before draining"; |
| return; |
| } |
| DCHECK(back_down_time_.IsInitialized()); |
| QuicTime::Delta buffer_reduction = |
| back_down_congestion_delay_.Subtract(estimated_congestion_delay); |
| QuicTime::Delta elapsed_time = |
| feedback_receive_time.Subtract(back_down_time_).Subtract(SmoothedRtt()); |
| |
| QuicBandwidth new_estimate = QuicBandwidth::Zero(); |
| if (buffer_reduction >= elapsed_time) { |
| // We have drained more than the elapsed time... go back to our old rate. |
| new_estimate = back_down_bandwidth_; |
| } else { |
| float fraction_of_rate = |
| static_cast<float>(buffer_reduction.ToMicroseconds()) / |
| elapsed_time.ToMicroseconds(); // < 1.0 |
| |
| QuicBandwidth draining_rate = back_down_bandwidth_.Scale(fraction_of_rate); |
| QuicBandwidth max_estimated_draining_rate = |
| back_down_bandwidth_.Subtract(current_bandwidth_); |
| if (draining_rate > max_estimated_draining_rate) { |
| // We drained faster than our old send rate, go back to our old rate. |
| new_estimate = back_down_bandwidth_; |
| } else { |
| // Use our drain rate and our kMinBitrateReduction to go to our |
| // new estimate. |
| new_estimate = std::max(current_bandwidth_, |
| current_bandwidth_.Add(draining_rate).Scale( |
| 1.0f - kMinBitrateReduction)); |
| DLOG(INFO) << "Draining calculation; current rate:" |
| << current_bandwidth_.ToKBitsPerSecond() << " Kbits/s " |
| << "draining rate:" |
| << draining_rate.ToKBitsPerSecond() << " Kbits/s " |
| << "new estimate:" |
| << new_estimate.ToKBitsPerSecond() << " Kbits/s " |
| << " buffer reduction:" |
| << buffer_reduction.ToMicroseconds() << " us " |
| << " elapsed time:" |
| << elapsed_time.ToMicroseconds() << " us "; |
| } |
| } |
| if (new_estimate == current_bandwidth_) { |
| return; |
| } |
| |
| QuicBandwidth channel_estimate = QuicBandwidth::Zero(); |
| ChannelEstimateState channel_estimator_state = |
| channel_estimator_->GetChannelEstimate(&channel_estimate); |
| |
| // TODO(pwestin): we need to analyze channel_estimate too. |
| switch (channel_estimator_state) { |
| case kChannelEstimateUnknown: |
| channel_estimate = current_bandwidth_; |
| break; |
| case kChannelEstimateUncertain: |
| channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin); |
| break; |
| case kChannelEstimateGood: |
| // Do nothing, estimate is accurate. |
| break; |
| } |
| bitrate_ramp_up_->Reset(new_estimate, back_down_bandwidth_, channel_estimate); |
| state_machine_->IncreaseBitrateDecision(); |
| paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, new_estimate); |
| current_bandwidth_ = new_estimate; |
| DLOG(INFO) << "New bandwidth estimate after draining:" |
| << new_estimate.ToKBitsPerSecond() << " Kbits/s"; |
| } |
| |
| void InterArrivalSender::EstimateBandwidthAfterDelayEvent( |
| QuicTime feedback_receive_time, |
| QuicTime::Delta estimated_congestion_delay) { |
| QuicByteCount estimated_byte_buildup = |
| current_bandwidth_.ToBytesPerPeriod(estimated_congestion_delay); |
| |
| // To drain all build up buffer within one RTT we need to reduce the |
| // bitrate with the following. |
| // TODO(pwestin): this is a crude first implementation. |
| int64 draining_rate_per_rtt = (estimated_byte_buildup * |
| kNumMicrosPerSecond) / SmoothedRtt().ToMicroseconds(); |
| |
| float decrease_factor = |
| draining_rate_per_rtt / current_bandwidth_.ToBytesPerSecond(); |
| |
| decrease_factor = std::max(decrease_factor, kMinBitrateReduction); |
| decrease_factor = std::min(decrease_factor, kMaxBitrateReduction); |
| back_down_congestion_delay_ = estimated_congestion_delay; |
| QuicBandwidth new_target_bitrate = |
| current_bandwidth_.Scale(1.0f - decrease_factor); |
| |
| // While in delay sensing mode send at least one packet per RTT. |
| QuicBandwidth min_delay_bitrate = |
| QuicBandwidth::FromBytesAndTimeDelta(max_segment_size_, SmoothedRtt()); |
| new_target_bitrate = std::max(new_target_bitrate, min_delay_bitrate); |
| |
| ResetCurrentBandwidth(feedback_receive_time, new_target_bitrate); |
| |
| DLOG(INFO) << "New bandwidth estimate after delay event:" |
| << current_bandwidth_.ToKBitsPerSecond() |
| << " Kbits/s min delay bitrate:" |
| << min_delay_bitrate.ToKBitsPerSecond() |
| << " Kbits/s RTT:" |
| << SmoothedRtt().ToMicroseconds() |
| << " us"; |
| } |
| |
| void InterArrivalSender::EstimateBandwidthAfterLossEvent( |
| QuicTime feedback_receive_time) { |
| ResetCurrentBandwidth(feedback_receive_time, |
| current_bandwidth_.Scale(kPacketLossBitrateReduction)); |
| DLOG(INFO) << "New bandwidth estimate after loss event:" |
| << current_bandwidth_.ToKBitsPerSecond() |
| << " Kbits/s"; |
| } |
| |
| void InterArrivalSender::ResetCurrentBandwidth(QuicTime feedback_receive_time, |
| QuicBandwidth new_rate) { |
| new_rate = std::max(new_rate, |
| QuicBandwidth::FromKBitsPerSecond(kMinBitrateKbit)); |
| QuicBandwidth channel_estimate = QuicBandwidth::Zero(); |
| ChannelEstimateState channel_estimator_state = |
| channel_estimator_->GetChannelEstimate(&channel_estimate); |
| |
| switch (channel_estimator_state) { |
| case kChannelEstimateUnknown: |
| channel_estimate = current_bandwidth_; |
| break; |
| case kChannelEstimateUncertain: |
| channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin); |
| break; |
| case kChannelEstimateGood: |
| // Do nothing. |
| break; |
| } |
| back_down_time_ = feedback_receive_time; |
| back_down_bandwidth_ = current_bandwidth_; |
| bitrate_ramp_up_->Reset(new_rate, current_bandwidth_, channel_estimate); |
| if (new_rate != current_bandwidth_) { |
| current_bandwidth_ = new_rate; |
| paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, |
| current_bandwidth_); |
| state_machine_->DecreaseBitrateDecision(); |
| } |
| } |
| |
| } // namespace net |