quic/congestion_control/BbrTesting.cpp (108 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <quic/congestion_control/BbrTesting.h> namespace quic { constexpr uint64_t kProbabilityThreshold = 75; constexpr uint64_t kNoCapacityPredictionCountThreshold = 100; BbrTestingCongestionController::BbrTestingCongestionController( QuicConnectionStateBase& conn) : BbrCongestionController(conn) { // Simulate 40 token bucket configurations with rates // from 250 kbps to 10 Mbps every 250 kbps. For each // configuration, the burst size is 1 second worth of bytes. // TODO: This list should be trimmed down. for (auto rate = 31250; rate <= 1250000; rate += 31250) { simulatedTBFVec_.emplace_back(rate, rate); } // Disable the ack aggregation logic BbrCongestionController::setExperimental(true); if (conn_.pacer) { conn_.pacer->setExperimental(true); } } // Limit the bandwidth based upon the measured TBF data. // If we have a simulated token bucket with probability>kProbabilityThreshold, // bandwdith estimate = // max(min(tbf_rate,long_term_bw_sampler_rate),short_term_sampler_rate) // This allows the returned bandwidth value to: // - Continue working normally when probability is low in any Token Bucket // config // - React to long-term drops in available bandwidth // - Make use of higher short-term bandwidth bursts // - Use the detected token bucket config otherwise // Note that due to using short_term_sampler_rate, we could continue // sending at a rate faster than that of the policer even after the burst has // expired. This can last for a maximum of 1-rtt. We can try to improve // on that later. Bandwidth BbrTestingCongestionController::bandwidth() const noexcept { const SimulatedTBF* maxTBF = &simulatedTBFVec_.front(); for (const auto& stbf : simulatedTBFVec_) { if (stbf.probability > maxTBF->probability) { maxTBF = &stbf; } } if (maxTBF->probability > kProbabilityThreshold) { return std::max( std::min( bandwidthSampler_->getBandwidth(), Bandwidth(maxTBF->rateBytesPerSecond, 1s)), bandwidthSampler_->getLatestSample()); } else { return BbrCongestionController::bandwidth(); } } void BbrTestingCongestionController::onPacketSent( const OutstandingPacket& packet) { BbrCongestionController::onPacketSent(packet); if (packet.packet.header.getPacketNumberSpace() != PacketNumberSpace::AppData) { return; } auto packetNum = packet.packet.header.getPacketSequenceNum(); auto maybeCreatedStatusVecItr = outstandingPacketTBFStatusMap_.emplace( std::piecewise_construct, std::forward_as_tuple(packetNum), std::forward_as_tuple()); // This should be a new key. A packet cannot be sent twice. CHECK(maybeCreatedStatusVecItr.second); auto& packetTBFStatus = maybeCreatedStatusVecItr.first->second; packetTBFStatus.reserve(simulatedTBFVec_.size()); auto pktSize = packet.metadata.encodedSize; for (auto& stbf : simulatedTBFVec_) { auto maybeQueueTime = stbf.tbf.consumeWithBorrowNonBlocking( pktSize, stbf.rateBytesPerSecond, stbf.burstBytes); auto hasCapacity = maybeQueueTime.value_or(0) == 0; packetTBFStatus.push_back(hasCapacity); if (!hasCapacity) { ++stbf.noCapacityPredictionCount; } } } void BbrTestingCongestionController::onPacketAckOrLoss( const AckEvent* FOLLY_NULLABLE ackEvent, const LossEvent* FOLLY_NULLABLE lossEvent) { BbrCongestionController::onPacketAckOrLoss(ackEvent, lossEvent); if (ackEvent) { for (const auto& ackedPacket : ackEvent->ackedPackets) { // Get the status of all the tested TBFs auto tbfStatsVecItr = outstandingPacketTBFStatusMap_.find(ackedPacket.packetNum); if (tbfStatsVecItr == outstandingPacketTBFStatusMap_.end()) { continue; } auto tbfStatsVec = std::move(tbfStatsVecItr->second); outstandingPacketTBFStatusMap_.erase(tbfStatsVecItr); // Check the TB predictions against the rateSample from this ack // Note: this uses the latest sample from the whole ack. I.e., one sample // is used for all the packets acked. This may not be the // accurate bandwidth measurement for all of them. Worst case, // this will delay our ability to detect the TB configuration by // 1-rtt after its burst has ended. auto bwSample = bandwidthSampler_->getLatestSample().normalize(); CHECK_EQ(tbfStatsVec.size(), simulatedTBFVec_.size()); for (size_t i = 0; i < tbfStatsVec.size(); i++) { auto& stbf = simulatedTBFVec_[i]; auto ackToTBFRatePercent = (bwSample * 100 / stbf.rateBytesPerSecond); // A TB prediction is true if one of these two conditions are met: // 1. The TB had tokens when the packet was sent and the ackRate is > // the rate of the TB (within 10%) // 2. The TB had no tokens when the packet was sent and the ackRate is // equal to the rate of the TB (within 10%) const bool stbfHadTokensOnSend = tbfStatsVec[i]; if (stbfHadTokensOnSend) { if (ackToTBFRatePercent > 90) { stbf.correctPredictionCount++; } else { stbf.incorrectPredictionCount++; } } else { if (ackToTBFRatePercent > 90 && ackToTBFRatePercent < 110) { stbf.correctPredictionCount++; } else { stbf.incorrectPredictionCount++; } } if (stbf.noCapacityPredictionCount > kNoCapacityPredictionCountThreshold) { // We've hit the capacity of this token bucket enough times to // calculate its probability stbf.probability = (stbf.correctPredictionCount * 100) / (stbf.correctPredictionCount + stbf.incorrectPredictionCount); if (stbf.probability > kProbabilityThreshold) { btlbwFound_ = true; } } } } } if (lossEvent) { for (auto lostPktNum : lossEvent->lostPacketNumbers) { outstandingPacketTBFStatusMap_.erase(lostPktNum); } } } } // namespace quic