1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-08-09 20:42:44 +03:00
Files
mvfst/quic/congestion_control/test/CopaTest.cpp
Yang Chi 537e178a5f Introduce Tokens in Quic Pacer
Summary:
Add a token value into the pacer. This is so that when there is not
enough application data to consume all the burst size in current event loop, we
can accumulate the unused sending credit and use the later when new data comes
in. Each time a packet is sent, we consume 1 token. On pakcet loss, we clear
all tokens. Each time there is an ack and we refresh pacing rate, token
increases by calculated burst size. It is also increased when timer drifts
during writes. When there is available tokens, there is no delay of writing out
packets, and the burst size is current token amount.

Reviewed By: siyengar

Differential Revision: D17670053

fbshipit-source-id: 6abc3acce39e0ece90248c52c3d73935a9878e02
2019-10-10 22:08:11 -07:00

475 lines
16 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its 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/Copa.h>
#include <folly/portability/GTest.h>
#include <quic/common/test/TestUtils.h>
#include <quic/state/test/Mocks.h>
using namespace testing;
namespace quic {
namespace test {
class CopaTest : public Test {
public:
CongestionController::LossEvent createLossEvent(
std::vector<std::pair<PacketNum, size_t>> lostPackets) {
CongestionController::LossEvent loss;
auto connId = getTestConnectionId();
uint64_t totalSentBytes = 0;
for (auto packetData : lostPackets) {
RegularQuicWritePacket packet(
ShortHeader(ProtectionType::KeyPhaseZero, connId, packetData.first));
totalSentBytes += 10;
loss.addLostPacket(OutstandingPacket(
std::move(packet), Clock::now(), 10, false, false, totalSentBytes));
loss.lostBytes = packetData.second;
}
loss.lostPackets = lostPackets.size();
return loss;
}
OutstandingPacket
createPacket(PacketNum packetNum, uint32_t size, uint64_t totalSent) {
auto connId = getTestConnectionId();
RegularQuicWritePacket packet(
ShortHeader(ProtectionType::KeyPhaseZero, connId, packetNum));
return OutstandingPacket(
std::move(packet), Clock::now(), size, false, false, totalSent);
}
CongestionController::AckEvent createAckEvent(
PacketNum largestAcked,
uint64_t ackedSize,
TimePoint ackTime) {
CongestionController::AckEvent ack;
ack.largestAckedPacket = largestAcked;
ack.ackTime = ackTime;
ack.ackedBytes = ackedSize;
ack.ackedPackets.push_back(createPacket(
largestAcked,
ackedSize,
ackedSize /* incorrect totalSent but works for this test */));
return ack;
}
uint64_t cwndChangeSteadyState(
uint64_t lastCwndBytes,
uint64_t velocity,
uint64_t packetSize,
double latencyFactor,
QuicServerConnectionState& conn) {
return 1.0 * packetSize * conn.udpSendPacketLen * velocity /
(latencyFactor * lastCwndBytes);
}
uint64_t
exitSlowStart(Copa& copa, QuicServerConnectionState& conn, TimePoint& now) {
auto numPacketsInFlight = 0;
auto packetNumToSend = 1;
auto packetSize = conn.udpSendPacketLen;
EXPECT_TRUE(copa.inSlowStart());
// send one cwnd worth packets in a burst
auto totalSent = 0;
while (copa.getWritableBytes() > 0) {
totalSent += packetSize;
copa.onPacketSent(createPacket(packetNumToSend, packetSize, totalSent));
numPacketsInFlight++;
EXPECT_EQ(copa.getBytesInFlight(), numPacketsInFlight * packetSize);
}
// Say you get ack for first packet after 50 ms
now += 50ms;
auto packetNumToAck = 1;
// update rtt measurements
conn.lossState.lrtt = 50ms;
// Rttmin = 50ms
conn.lossState.srtt = 50ms;
// ack for first packet, lastCwndDoubleTime_ will be initialized now
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
numPacketsInFlight--;
EXPECT_EQ(copa.getBytesInFlight(), numPacketsInFlight * packetSize);
now += 100ms;
packetNumToAck++;
EXPECT_TRUE(copa.inSlowStart());
auto lastCwnd = copa.getCongestionWindow();
// ack second packet
// update rtt measurements, current rate > target rate at this point, so it
// exits slow start
// target rate = 20 packets per sec, current rate = 10 packets / 100ms = 50
// packets per second
conn.lossState.lrtt = 150ms;
// Rttmin = 50ms
conn.lossState.srtt = 100ms;
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
EXPECT_FALSE(copa.inSlowStart());
uint64_t cwndChange =
cwndChangeSteadyState(lastCwnd, 1.0, packetSize, 0.5, conn);
// cwnd = 10 - 1 / (0.5 * 10) = 9.8 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd - cwndChange);
return copa.getCongestionWindow();
}
};
TEST_F(CopaTest, TestWritableBytes) {
QuicServerConnectionState conn;
Copa copa(conn);
EXPECT_TRUE(copa.inSlowStart());
conn.lossState.largestSent = 5;
PacketNum ackPacketNum = 6;
uint64_t writableBytes = copa.getWritableBytes();
copa.onPacketSent(
createPacket(ackPacketNum, writableBytes - 10, writableBytes - 10));
EXPECT_EQ(copa.getWritableBytes(), 10);
copa.onPacketSent(createPacket(ackPacketNum, 20, writableBytes + 10));
EXPECT_EQ(copa.getWritableBytes(), 0);
}
TEST_F(CopaTest, PersistentCongestion) {
QuicServerConnectionState conn;
Copa copa(conn);
auto qLogger = std::make_shared<FileQLogger>();
conn.qLogger = qLogger;
EXPECT_TRUE(copa.inSlowStart());
conn.lossState.largestSent = 5;
PacketNum ackPacketNum = 6;
uint32_t ackedSize = 10;
auto pkt = createPacket(ackPacketNum, ackedSize, ackedSize);
copa.onPacketSent(pkt);
std::vector<int> indices =
getQLogEventIndices(QLogEventType::CongestionMetricUpdate, qLogger);
EXPECT_EQ(indices.size(), 1);
auto tmp = std::move(qLogger->logs[indices[0]]);
auto event = dynamic_cast<QLogCongestionMetricUpdateEvent*>(tmp.get());
EXPECT_EQ(event->bytesInFlight, 10);
EXPECT_EQ(event->currentCwnd, kDefaultCwnd);
EXPECT_EQ(event->congestionEvent, kCongestionPacketSent);
CongestionController::LossEvent loss;
loss.persistentCongestion = true;
loss.addLostPacket(pkt);
copa.onPacketAckOrLoss(folly::none, loss);
EXPECT_EQ(
copa.getWritableBytes(),
conn.transportSettings.minCwndInMss * conn.udpSendPacketLen);
EXPECT_TRUE(copa.inSlowStart());
}
TEST_F(CopaTest, RemoveBytesWithoutLossOrAck) {
QuicServerConnectionState conn;
Copa copa(conn);
auto qLogger = std::make_shared<FileQLogger>();
conn.qLogger = qLogger;
EXPECT_TRUE(copa.inSlowStart());
auto originalWritableBytes = copa.getWritableBytes();
conn.lossState.largestSent = 5;
PacketNum ackPacketNum = 6;
uint32_t ackedSize = 10;
copa.onPacketSent(createPacket(ackPacketNum, ackedSize, ackedSize));
copa.onRemoveBytesFromInflight(2);
EXPECT_EQ(copa.getWritableBytes(), originalWritableBytes - ackedSize + 2);
std::vector<int> indices =
getQLogEventIndices(QLogEventType::CongestionMetricUpdate, qLogger);
EXPECT_EQ(indices.size(), 2);
auto tmp = std::move(qLogger->logs[indices[0]]);
auto event = dynamic_cast<QLogCongestionMetricUpdateEvent*>(tmp.get());
EXPECT_EQ(event->bytesInFlight, ackedSize);
EXPECT_EQ(event->currentCwnd, kDefaultCwnd);
EXPECT_EQ(event->congestionEvent, kCongestionPacketSent);
auto tmp2 = std::move(qLogger->logs[indices[1]]);
auto event2 = dynamic_cast<QLogCongestionMetricUpdateEvent*>(tmp2.get());
EXPECT_EQ(event2->bytesInFlight, ackedSize - 2);
EXPECT_EQ(event2->currentCwnd, kDefaultCwnd);
EXPECT_EQ(event2->congestionEvent, kRemoveInflight);
}
TEST_F(CopaTest, TestSlowStartAck) {
QuicServerConnectionState conn;
Copa copa(conn);
auto qLogger = std::make_shared<FileQLogger>();
conn.qLogger = qLogger;
EXPECT_TRUE(copa.inSlowStart());
// initial cwnd = 10 packets
EXPECT_EQ(
copa.getCongestionWindow(),
conn.transportSettings.initCwndInMss * conn.udpSendPacketLen);
auto numPacketsInFlight = 0;
auto packetNumToSend = 1;
auto packetSize = conn.udpSendPacketLen;
auto now = Clock::now();
uint64_t totalSent = 0;
// send one cwnd worth packets in a burst
while (copa.getWritableBytes() > 0) {
totalSent += packetSize;
copa.onPacketSent(createPacket(packetNumToSend, packetSize, totalSent));
numPacketsInFlight++;
EXPECT_EQ(copa.getBytesInFlight(), numPacketsInFlight * packetSize);
}
// Say you get ack for first packet after 280 ms
now += 280ms;
auto packetNumToAck = 1;
// update rtt measurements
conn.lossState.lrtt = 280ms;
// RTTmin = 280ms
conn.lossState.srtt = 280ms;
// ack for first packet, lastCwndDoubleTime_ will be initialized now
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
numPacketsInFlight--;
EXPECT_EQ(copa.getBytesInFlight(), numPacketsInFlight * packetSize);
std::vector<int> indices =
getQLogEventIndices(QLogEventType::CongestionMetricUpdate, qLogger);
EXPECT_EQ(indices.size(), 11); // mostly congestionPacketSent logs
auto tmp = std::move(qLogger->logs[indices[10]]);
auto event = dynamic_cast<QLogCongestionMetricUpdateEvent*>(tmp.get());
EXPECT_EQ(event->bytesInFlight, copa.getBytesInFlight());
EXPECT_EQ(event->currentCwnd, kDefaultCwnd);
EXPECT_EQ(event->congestionEvent, kCongestionPacketAck);
now += 50ms;
packetNumToAck++;
EXPECT_TRUE(copa.inSlowStart());
auto lastCwnd = copa.getCongestionWindow();
// Say more time passed and some packets were acked meanwhile.
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
now += 300ms;
// update rtt measurements
conn.lossState.lrtt = 300ms;
// RTTmin = 280ms
conn.lossState.srtt = 300ms;
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
now += 100ms;
EXPECT_TRUE(copa.inSlowStart());
// cwnd = 20 packets
EXPECT_EQ(copa.getCongestionWindow(), 2 * lastCwnd);
lastCwnd = copa.getCongestionWindow();
// ack for 5th packet, at this point currentRate < targetRate, but not enough
// time has passed for cwnd to double again in slow start
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
EXPECT_TRUE(copa.inSlowStart());
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd);
packetNumToAck++;
now += 100ms;
// update rtt measurements
conn.lossState.lrtt = 350ms;
// RTTmin = 280ms
conn.lossState.srtt = 300ms;
// ack for 6th packet, at this point currentRate > targetRate, so it woudl
// exit slow start and reduce cwnd
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
EXPECT_FALSE(copa.inSlowStart());
EXPECT_LE(copa.getCongestionWindow(), lastCwnd);
}
TEST_F(CopaTest, TestSteadyStateChanges) {
QuicServerConnectionState conn;
Copa copa(conn);
auto now = Clock::now();
auto lastCwnd = exitSlowStart(copa, conn, now);
auto packetSize = conn.udpSendPacketLen;
auto packetNumToAck = 10;
now += 10ms;
conn.lossState.lrtt = 100ms;
// Rttmin = 100ms
conn.lossState.srtt = 100ms;
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
uint64_t cwndChange =
cwndChangeSteadyState(lastCwnd, 1.0, packetSize, 0.5, conn);
// cwnd = 9.8 - 1 / (0.5 * 9.8) = 9.6 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd - cwndChange);
lastCwnd = copa.getCongestionWindow();
now += 10ms;
// target rate = 200 packets per sec, current rate = 9.6 packets / 100ms = ~50
// packets per second
conn.lossState.lrtt = 60ms;
// Rttmin = 60ms
conn.lossState.srtt = 100ms;
copa.onPacketAckOrLoss(
createAckEvent(packetNumToAck, packetSize, now), folly::none);
packetNumToAck++;
cwndChange = cwndChangeSteadyState(lastCwnd, 1.0, packetSize, 0.5, conn);
// cwnd = 9.6 + 1 / (0.5 * 9.6) = 9.8 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
}
TEST_F(CopaTest, TestVelocity) {
QuicServerConnectionState conn;
conn.transportSettings.pacingTimerTickInterval = 10ms;
Copa copa(conn);
auto qLogger = std::make_shared<FileQLogger>();
conn.qLogger = qLogger;
conn.transportSettings.pacingEnabled = true;
// lastCwnd = 9.8 packets
auto now = Clock::now();
auto lastCwnd = exitSlowStart(copa, conn, now);
uint64_t velocity = 1.0;
auto packetSize = conn.udpSendPacketLen;
// target rate = 200 packets per sec, current rate = 9.8 packets / 100ms = ~50
// packets per second.
conn.lossState.lrtt = 60ms;
// Rttmin = 60ms
conn.lossState.srtt = 100ms;
now += 100ms;
// velocity = 1, direction = 0
copa.onPacketAckOrLoss(createAckEvent(30, packetSize, now), folly::none);
uint64_t cwndChange =
cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 9.8 + 1 / (0.5 * 9.8) = 10 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 1, direction 0 -> 1
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(35, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 10 + 1 / (0.5 * 10) = 10.2 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 1, direction = 1
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(40, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 10.2 + 1 / (0.5 * 10.2) = 10.4 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 1, direction = 1
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(45, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 10.4 + 1 / (0.5 * 10.4) = 10.6 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 2, direction = 1
velocity = 2 * velocity;
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(50, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 10 + 2 / (0.5 * 10.6) = 11 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 4, direction = 1
velocity = 2 * velocity;
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(50, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 11 + 4 / (0.5 * 11) = 11.8 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// another ack, velocity = 8, direction = 1
velocity = 2 * velocity;
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(50, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 11.8 + 8 / (0.5 * 11.8) = 13.4 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd + cwndChange);
lastCwnd = copa.getCongestionWindow();
// drop target rate, verify that velocity resets
conn.lossState.lrtt = 200ms;
// Rttmin = 60ms
conn.lossState.srtt = 100ms;
velocity = 1;
now += 100ms;
copa.onPacketAckOrLoss(createAckEvent(50, packetSize, now), folly::none);
cwndChange = cwndChangeSteadyState(lastCwnd, velocity, packetSize, 0.5, conn);
// cwnd = 11.8 + 8 / (0.5 * 11.8) = 13.4 packets
EXPECT_EQ(copa.getCongestionWindow(), lastCwnd - cwndChange);
lastCwnd = copa.getCongestionWindow();
}
TEST_F(CopaTest, NoLargestAckedPacketNoCrash) {
QuicServerConnectionState conn;
Copa copa(conn);
auto qLogger = std::make_shared<FileQLogger>();
conn.qLogger = qLogger;
CongestionController::LossEvent loss;
loss.largestLostPacketNum = 0;
CongestionController::AckEvent ack;
copa.onPacketAckOrLoss(ack, loss);
std::vector<int> indices =
getQLogEventIndices(QLogEventType::CongestionMetricUpdate, qLogger);
EXPECT_EQ(indices.size(), 1);
auto tmp = std::move(qLogger->logs[indices[0]]);
auto event = dynamic_cast<QLogCongestionMetricUpdateEvent*>(tmp.get());
EXPECT_EQ(event->bytesInFlight, copa.getBytesInFlight());
EXPECT_EQ(event->currentCwnd, kDefaultCwnd);
EXPECT_EQ(event->congestionEvent, kCongestionPacketLoss);
}
TEST_F(CopaTest, PacketLossInvokesPacer) {
QuicServerConnectionState conn;
Copa copa(conn);
auto mockPacer = std::make_unique<MockPacer>();
auto rawPacer = mockPacer.get();
conn.pacer = std::move(mockPacer);
auto packet = createPacket(0 /* pacetNum */, 1000, 1000);
copa.onPacketSent(packet);
EXPECT_CALL(*rawPacer, onPacketsLoss()).Times(1);
CongestionController::LossEvent lossEvent;
lossEvent.addLostPacket(packet);
copa.onPacketAckOrLoss(folly::none, lossEvent);
}
} // namespace test
} // namespace quic