Add instrumentation callback for pmtu blackhole and upperbound
Summary:
This adds two events that we care mostly about d6d's perf: detecting blackhole
and detecting upperbound.
MODIFY
quic/api/Observer.h
quic/api/Observer.h
#include <quic/QuicException.h>
#include <quic/common/SmallVec.h>
+#include <quic/d6d/Types.h>
#include <quic/state/OutstandingPacket.h>
namespace quic {
@@ -72,6 +73,62 @@ class InstrumentationObserver {
lastAckedPacketInfo;
};
+ struct PMTUBlackholeEvent {
+ explicit PMTUBlackholeEvent(
+ TimePoint blackholeTimeIn,
+ std::chrono::microseconds timeSinceLastNonSearchStateIn,
+ D6DMachineState lastNonSearchStateIn,
+ D6DMachineState currentStateIn,
+ uint64_t udpSendPacketLenIn,
+ uint64_t lastProbeSizeIn,
+ uint64_t blackholeDetectionWindowIn,
+ uint64_t blackholeDetectionThresholdIn,
+ const quic::OutstandingPacket& pkt)
+ : blackholeTime(blackholeTimeIn),
+ timeSinceLastNonSearchState(timeSinceLastNonSearchStateIn),
+ lastNonSearchState(lastNonSearchStateIn),
+ currentState(currentStateIn),
+ udpSendPacketLen(udpSendPacketLenIn),
+ lastProbeSize(lastProbeSizeIn),
+ blackholeDetectionWindow(blackholeDetectionWindowIn),
+ blackholeDetectionThreshold(blackholeDetectionThresholdIn),
+ triggeringPacketMetadata(pkt.metadata) {}
+ TimePoint blackholeTime;
+ // How long since last "stable" state
+ std::chrono::microseconds timeSinceLastNonSearchState;
+ D6DMachineState lastNonSearchState;
+ D6DMachineState currentState;
+ uint64_t udpSendPacketLen;
+ uint64_t lastProbeSize;
+ uint64_t blackholeDetectionWindow;
+ uint64_t blackholeDetectionThreshold;
+ // The metadata of the packet that triggerred blackhole signal
+ const quic::OutstandingPacketMetadata triggeringPacketMetadata;
+ };
+
+ struct PMTUUpperBoundEvent {
+ explicit PMTUUpperBoundEvent(
+ TimePoint upperBoundTimeIn,
+ std::chrono::microseconds timeSinceLastNonSearchStateIn,
+ D6DMachineState lastNonSearchStateIn,
+ uint64_t upperBoundPMTUIn,
+ uint64_t cumulativeProbesSentIn,
+ ProbeSizeRaiserType probeSizeRaiserTypeIn)
+ : upperBoundTime(upperBoundTimeIn),
+ timeSinceLastNonSearchState(timeSinceLastNonSearchStateIn),
+ lastNonSearchState(lastNonSearchStateIn),
+ upperBoundPMTU(upperBoundPMTUIn),
+ cumulativeProbesSent(cumulativeProbesSentIn),
+ probeSizeRaiserType(probeSizeRaiserTypeIn) {}
+ TimePoint upperBoundTime;
+ // How long it took to reach upperbound
+ std::chrono::microseconds timeSinceLastNonSearchState;
+ D6DMachineState lastNonSearchState;
+ uint64_t upperBoundPMTU;
+ uint64_t cumulativeProbesSent;
+ ProbeSizeRaiserType probeSizeRaiserType;
+ };
+
virtual ~InstrumentationObserver() = default;
/**
@@ -110,6 +167,24 @@ class InstrumentationObserver {
virtual void rttSampleGenerated(
QuicSocket*, /* socket */
const PacketRTT& /* RTT sample */) {}
+
+ /**
+ * pmtuBlackholeDetected() is invoked when a PMTU blackhole is detected.
+ *
+ * @param pmtuBlackholeEvent const reference to the PMTU blackhole event
+ */
+ virtual void pmtuBlackholeDetected(
+ QuicSocket*, /* socket */
+ const PMTUBlackholeEvent& /* pmtuBlackholeEvent */) {}
+
+ /**
+ * pmtuUpperBoundDetected() is invoked when a PMTU upperbound is detected.
+ *
+ * @param pmtuUpperBoundEvent const reference to the PMTU upperbound event
+ */
+ virtual void pmtuUpperBoundDetected(
+ QuicSocket*, /* socket */
+ const PMTUUpperBoundEvent& /* pmtuUpperBoundEvent */) {}
};
// Container for instrumentation observers.

MODIFY
quic/api/test/Mocks.h
quic/api/test/Mocks.h
@@ -338,6 +338,18 @@ class MockInstrumentationObserver : public InstrumentationObserver {
,
rttSampleGenerated,
void(QuicSocket*, const PacketRTT&));
+ GMOCK_METHOD2_(
+ ,
+ noexcept,
+ ,
+ pmtuBlackholeDetected,
+ void(QuicSocket*, const PMTUBlackholeEvent&));
+ GMOCK_METHOD2_(
+ ,
+ noexcept,
+ ,
+ pmtuUpperBoundDetected,
+ void(QuicSocket*, const PMTUUpperBoundEvent&));
static auto getLossPacketMatcher(bool reorderLoss, bool timeoutLoss) {
return AllOf(

MODIFY
quic/d6d/QuicD6DStateFunctions.cpp
quic/d6d/QuicD6DStateFunctions.cpp
@@ -16,6 +16,67 @@ inline void scheduleProbeAfterDelay(
conn.pendingEvents.d6d.sendProbeDelay = delay;
}
+/**
+ * A helper function that reports PMTU Upper Bound event.
+ * Returns the event timestamp
+ */
+static TimePoint reportUpperBound(QuicConnectionStateBase& conn) {
+ auto& d6d = conn.d6d;
+ const auto lastProbeSize = d6d.lastProbe->packetSize;
+ const auto now = Clock::now();
+ if (conn.instrumentationObservers_.size() > 0) {
+ InstrumentationObserver::PMTUUpperBoundEvent upperBoundEvent(
+ now,
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ now - d6d.meta.timeLastNonSearchState),
+ d6d.meta.lastNonSearchState,
+ lastProbeSize,
+ d6d.meta.totalTxedProbes,
+ conn.transportSettings.d6dConfig.raiserType);
+ // enqueue a function for every observer to invoke callback
+ for (const auto& observer : conn.instrumentationObservers_) {
+ conn.pendingCallbacks.emplace_back(
+ [observer, upperBoundEvent](QuicSocket* qSocket) {
+ observer->pmtuUpperBoundDetected(qSocket, upperBoundEvent);
+ });
+ }
+ }
+ return now;
+}
+
+/**
+ * Helper function that reports PMTU blackhole event.
+ * Returns the event timestamp
+ */
+static TimePoint reportBlackhole(
+ QuicConnectionStateBase& conn,
+ const OutstandingPacket& packet) {
+ auto& d6d = conn.d6d;
+ const auto now = Clock::now();
+ if (conn.instrumentationObservers_.size() > 0) {
+ InstrumentationObserver::PMTUBlackholeEvent blackholeEvent(
+ now,
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ now - d6d.meta.timeLastNonSearchState),
+ d6d.meta.lastNonSearchState,
+ d6d.state,
+ conn.udpSendPacketLen,
+ d6d.lastProbe->packetSize,
+ d6d.thresholdCounter->getWindow(),
+ d6d.thresholdCounter->getThreshold(),
+ packet);
+
+ // If there are observers, enqueue a function to invoke callback
+ for (const auto& observer : conn.instrumentationObservers_) {
+ conn.pendingCallbacks.emplace_back(
+ [observer, blackholeEvent](QuicSocket* qSocket) {
+ observer->pmtuBlackholeDetected(qSocket, blackholeEvent);
+ });
+ }
+ }
+ return now;
+}
+
void onD6DProbeTimeoutExpired(QuicConnectionStateBase& conn) {
onD6DLastProbeLost(conn);
}
@@ -46,6 +107,8 @@ void onD6DLastProbeAcked(QuicConnectionStateBase& conn) {
CHECK(maybeNextProbeSize.hasValue());
d6d.currentProbeSize = *maybeNextProbeSize;
d6d.state = D6DMachineState::SEARCHING;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = Clock::now();
scheduleProbeAfterDelay(conn, kDefaultD6DProbeDelayWhenAcked);
break;
case D6DMachineState::SEARCHING:
@@ -67,6 +130,7 @@ void onD6DLastProbeAcked(QuicConnectionStateBase& conn) {
d6d.state = D6DMachineState::SEARCH_COMPLETE;
conn.pendingEvents.d6d.scheduleRaiseTimeout = true;
conn.pendingEvents.d6d.scheduleProbeTimeout = false;
+ reportUpperBound(conn);
}
break;
case D6DMachineState::ERROR:
@@ -74,6 +138,8 @@ void onD6DLastProbeAcked(QuicConnectionStateBase& conn) {
// We should try sending base pmtu-sized packet now.
d6d.currentProbeSize = d6d.basePMTU;
d6d.state = D6DMachineState::BASE;
+ d6d.meta.lastNonSearchState = D6DMachineState::ERROR;
+ d6d.meta.timeLastNonSearchState = Clock::now();
scheduleProbeAfterDelay(conn, kDefaultD6DProbeDelayWhenAcked);
break;
default:
@@ -90,6 +156,8 @@ void onD6DLastProbeLost(QuicConnectionStateBase& conn) {
// remedy is to probe much smaller
d6d.state = D6DMachineState::ERROR;
d6d.currentProbeSize = kMinMaxUDPPayload;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = Clock::now();
// TODO: reduce udpSendPacketLen below base once we can trust
// this signal. Currently assuming quic is going to terminate
@@ -104,6 +172,8 @@ void onD6DLastProbeLost(QuicConnectionStateBase& conn) {
// that the upper bound is reached
d6d.state = D6DMachineState::SEARCH_COMPLETE;
conn.pendingEvents.d6d.scheduleRaiseTimeout = true;
+ conn.pendingEvents.d6d.scheduleProbeTimeout = false;
+ reportUpperBound(conn);
return;
}
// Otherwise, the loss could be due to congestion, so we keep
@@ -149,10 +219,15 @@ void detectPMTUBlackhole(
.count())) {
LOG(ERROR) << "PMTU blackhole detected on packet loss, reducing PMTU from "
<< conn.udpSendPacketLen << " to base " << d6d.basePMTU;
+
+ auto eventTime = reportBlackhole(conn, packet);
+ if (d6d.state != D6DMachineState::SEARCHING) {
+ d6d.meta.lastNonSearchState = d6d.state;
+ d6d.meta.timeLastNonSearchState = eventTime;
+ }
d6d.state = D6DMachineState::BASE;
d6d.currentProbeSize = d6d.basePMTU;
conn.udpSendPacketLen = d6d.basePMTU;
-
// Cancel existing raise timeout if any
conn.pendingEvents.d6d.scheduleRaiseTimeout = false;
}

MODIFY
quic/d6d/test/QuicD6DStateFunctionsTest.cpp
quic/d6d/test/QuicD6DStateFunctionsTest.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <quic/api/test/Mocks.h>
#include <quic/common/test/TestUtils.h>
#include <quic/d6d/QuicD6DStateFunctions.h>
#include <quic/d6d/test/Mocks.h>
#include <quic/state/StateData.h>
using namespace testing;
+using namespace std::literals::chrono_literals;
namespace quic {
namespace test {
+// timeLastNonSearchState can only increase
+enum class TimeLastNonSearchStateEnd : uint8_t { EQ, GE };
+
struct D6DProbeLostTestFixture {
D6DMachineState stateBegin;
D6DMachineState stateEnd;
@@ -29,6 +34,11 @@ struct D6DProbeLostTestFixture {
uint64_t outstandingProbes;
uint32_t currentProbeSizeBegin;
uint32_t currentProbeSizeEnd;
+ D6DProbePacket lastProbe;
+ D6DMachineState lastNonSearchStateBegin;
+ D6DMachineState lastNonSearchStateEnd;
+ TimePoint timeLastNonSearchStateBegin;
+ TimeLastNonSearchStateEnd timeLastNonSearchStateEndE;
};
RegularQuicWritePacket makeTestShortPacket() {
@@ -46,7 +56,10 @@ class QuicD6DStateFunctionsTest : public Test {
conn.d6d.state = fixture.stateBegin;
conn.d6d.outstandingProbes = fixture.outstandingProbes;
conn.d6d.currentProbeSize = fixture.currentProbeSizeBegin;
+ conn.d6d.lastProbe = fixture.lastProbe;
conn.pendingEvents.d6d.sendProbePacket = fixture.sendProbeBegin;
+ conn.d6d.meta.lastNonSearchState = fixture.lastNonSearchStateBegin;
+ conn.d6d.meta.timeLastNonSearchState = fixture.timeLastNonSearchStateBegin;
onD6DLastProbeLost(conn);
EXPECT_EQ(conn.d6d.state, fixture.stateEnd);
EXPECT_EQ(conn.d6d.currentProbeSize, fixture.currentProbeSizeEnd);
@@ -57,11 +70,24 @@ class QuicD6DStateFunctionsTest : public Test {
} else {
ASSERT_FALSE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
}
+ EXPECT_EQ(conn.d6d.meta.lastNonSearchState, fixture.lastNonSearchStateEnd);
+ switch (fixture.timeLastNonSearchStateEndE) {
+ case TimeLastNonSearchStateEnd::EQ:
+ EXPECT_EQ(
+ conn.d6d.meta.timeLastNonSearchState,
+ fixture.timeLastNonSearchStateBegin);
+ break;
+ default:
+ EXPECT_GE(
+ conn.d6d.meta.timeLastNonSearchState,
+ fixture.timeLastNonSearchStateBegin);
+ }
}
};
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInBase) {
QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto now = Clock::now();
// One probe lost in BASE state
D6DProbeLostTestFixture oneProbeLostInBase = {
D6DMachineState::BASE, // stateBegin
@@ -70,13 +96,19 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInBase) {
kDefaultD6DProbeDelayWhenLost, // sendProbeEnd
1, // outstandingProbes
conn.d6d.basePMTU, // currentProbeSizeBegin
- conn.d6d.basePMTU // currentProbeSizeEnd
+ conn.d6d.basePMTU, // currentProbeSizeEnd
+ D6DProbePacket(0, conn.d6d.basePMTU + 10),
+ D6DMachineState::DISABLED, // lastNonSearchStateBegin
+ D6DMachineState::DISABLED, // lastNonSearchStateEnd
+ now, // timeLastNonSearchStateBegin
+ TimeLastNonSearchStateEnd::EQ // timeLastNonSearchStateEndE
};
runD6DProbeLostTest(conn, oneProbeLostInBase);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInBase) {
QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto now = Clock::now();
// max number of probes lost in BASE state
D6DProbeLostTestFixture maxNumProbesLostInBase = {
D6DMachineState::BASE,
@@ -85,12 +117,18 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInBase) {
kDefaultD6DProbeDelayWhenLost,
kDefaultD6DMaxOutstandingProbes,
conn.d6d.basePMTU,
- kMinMaxUDPPayload};
+ kMinMaxUDPPayload,
+ D6DProbePacket(0, conn.d6d.basePMTU + 10),
+ D6DMachineState::DISABLED,
+ D6DMachineState::BASE,
+ now,
+ TimeLastNonSearchStateEnd::GE};
runD6DProbeLostTest(conn, maxNumProbesLostInBase);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInSearching) {
QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto now = Clock::now();
// One probe lots in SEARCHING state
D6DProbeLostTestFixture oneProbeLostInSearching = {
D6DMachineState::SEARCHING,
@@ -99,12 +137,18 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInSearching) {
kDefaultD6DProbeDelayWhenLost,
1,
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
- static_cast<uint32_t>(conn.d6d.basePMTU + 10)};
+ static_cast<uint32_t>(conn.d6d.basePMTU + 10),
+ D6DProbePacket(0, conn.d6d.basePMTU + 10),
+ D6DMachineState::BASE,
+ D6DMachineState::BASE,
+ now,
+ TimeLastNonSearchStateEnd::EQ};
runD6DProbeLostTest(conn, oneProbeLostInSearching);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInSearching) {
QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto now = Clock::now();
// Max number of probes lost in SEARCHING state
D6DProbeLostTestFixture maxProbesLostInSearching = {
D6DMachineState::SEARCHING,
@@ -113,12 +157,18 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInSearching) {
folly::none,
kDefaultD6DMaxOutstandingProbes,
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
- static_cast<uint32_t>(conn.d6d.basePMTU + 10)};
+ static_cast<uint32_t>(conn.d6d.basePMTU + 10),
+ D6DProbePacket(0, conn.d6d.basePMTU + 10),
+ D6DMachineState::BASE,
+ D6DMachineState::BASE,
+ now,
+ TimeLastNonSearchStateEnd::EQ};
runD6DProbeLostTest(conn, maxProbesLostInSearching);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInError) {
QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto now = Clock::now();
// Probe lost in ERROR state
D6DProbeLostTestFixture probeLostInError = {
D6DMachineState::ERROR,
@@ -127,7 +177,12 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInError) {
kDefaultD6DProbeDelayWhenLost,
kDefaultD6DMaxOutstandingProbes + 1,
kMinMaxUDPPayload,
- kMinMaxUDPPayload};
+ kMinMaxUDPPayload,
+ D6DProbePacket(0, conn.d6d.basePMTU + 10),
+ D6DMachineState::BASE,
+ D6DMachineState::BASE,
+ now,
+ TimeLastNonSearchStateEnd::EQ};
runD6DProbeLostTest(conn, probeLostInError);
}
@@ -135,15 +190,22 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInBase) {
QuicConnectionStateBase conn(QuicNodeType::Server);
const uint16_t expectPMTU = 1400;
auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
d6d.state = D6DMachineState::BASE;
d6d.outstandingProbes = 1;
d6d.currentProbeSize = d6d.basePMTU;
+ d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
+ d6d.meta.timeLastNonSearchState = now;
auto pkt = OutstandingPacket(
makeTestShortPacket(),
Clock::now(),
d6d.currentProbeSize,
false,
true,
+ d6d.currentProbeSize,
d6d.currentProbeSize);
d6d.lastProbe = D6DProbePacket(
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
@@ -152,26 +214,40 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInBase) {
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
.Times(1)
.WillOnce(Return(expectPMTU));
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuUpperBoundDetected(_, _))
+ .Times(0);
onD6DLastProbeAcked(conn);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
+ EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingOne) {
QuicConnectionStateBase conn(QuicNodeType::Server);
const uint16_t expectPMTU = 1400;
auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
d6d.state = D6DMachineState::SEARCHING;
d6d.outstandingProbes = 1;
conn.udpSendPacketLen = 1250;
d6d.currentProbeSize = 1300;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = now;
auto pkt = OutstandingPacket(
makeTestShortPacket(),
Clock::now(),
d6d.currentProbeSize,
false,
true,
+ d6d.currentProbeSize,
d6d.currentProbeSize);
d6d.lastProbe = D6DProbePacket(
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
@@ -180,26 +256,41 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingOne) {
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
.Times(1)
.WillOnce(Return(expectPMTU));
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuUpperBoundDetected(_, _))
+ .Times(0);
onD6DLastProbeAcked(conn);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
EXPECT_EQ(conn.udpSendPacketLen, 1300);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
+ EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingMax) {
QuicConnectionStateBase conn(QuicNodeType::Server);
const uint16_t oversize = 1500;
auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
d6d.state = D6DMachineState::SEARCHING;
d6d.outstandingProbes = 3;
conn.udpSendPacketLen = 1400;
d6d.currentProbeSize = 1450;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = now;
+ d6d.meta.totalTxedProbes = 10;
auto pkt = OutstandingPacket(
makeTestShortPacket(),
Clock::now(),
d6d.currentProbeSize,
false,
true,
+ d6d.currentProbeSize,
d6d.currentProbeSize);
d6d.lastProbe = D6DProbePacket(
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
@@ -208,25 +299,50 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingMax) {
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
.Times(1)
.WillOnce(Return(oversize));
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuUpperBoundDetected(_, _))
+ .Times(1)
+ .WillOnce(Invoke(
+ [&](QuicSocket* /* qSocket */,
+ const InstrumentationObserver::PMTUUpperBoundEvent& event) {
+ EXPECT_LT(now, event.upperBoundTime);
+ EXPECT_LT(0us, event.timeSinceLastNonSearchState);
+ EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
+ EXPECT_EQ(1450, event.upperBoundPMTU);
+ EXPECT_EQ(10, event.cumulativeProbesSent);
+ EXPECT_EQ(
+ ProbeSizeRaiserType::ConstantStep, event.probeSizeRaiserType);
+ }));
onD6DLastProbeAcked(conn);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
EXPECT_EQ(d6d.currentProbeSize, 1450);
EXPECT_EQ(conn.udpSendPacketLen, 1450);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
+ EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
}
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInError) {
QuicConnectionStateBase conn(QuicNodeType::Server);
auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
d6d.state = D6DMachineState::ERROR;
d6d.outstandingProbes = 3;
conn.udpSendPacketLen = d6d.basePMTU;
d6d.currentProbeSize = d6d.basePMTU - 20;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = now;
auto pkt = OutstandingPacket(
makeTestShortPacket(),
Clock::now(),
d6d.currentProbeSize,
false,
true,
+ d6d.currentProbeSize,
d6d.currentProbeSize);
d6d.lastProbe = D6DProbePacket(
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
@@ -235,10 +351,144 @@ TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInError) {
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
.Times(1)
.WillOnce(Return(1300)); // Won't be used
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuUpperBoundDetected(_, _))
+ .Times(0);
onD6DLastProbeAcked(conn);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
+ EXPECT_EQ(d6d.state, D6DMachineState::BASE);
+ EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
+ EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::ERROR);
+ EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
+}
+
+TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearching) {
+ QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
+ d6d.state = D6DMachineState::SEARCHING;
+ d6d.outstandingProbes = 2;
+ conn.udpSendPacketLen = d6d.basePMTU + 20;
+ d6d.currentProbeSize = d6d.basePMTU + 30;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = now;
+ auto pkt = OutstandingPacket(
+ makeTestShortPacket(),
+ now + 10s,
+ d6d.currentProbeSize,
+ false,
+ true,
+ d6d.currentProbeSize,
+ d6d.currentProbeSize);
+ d6d.lastProbe = D6DProbePacket(
+ pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
+
+ auto lostPacket = OutstandingPacket(
+ makeTestShortPacket(),
+ now + 8s,
+ conn.udpSendPacketLen,
+ false,
+ conn.udpSendPacketLen + d6d.currentProbeSize,
+ conn.udpSendPacketLen + d6d.currentProbeSize);
+
+ d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
+ std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
+ 1); // Threshold of 1 will cause window to be set to 0
+
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuBlackholeDetected(_, _))
+ .Times(1)
+ .WillOnce(
+ Invoke([&](QuicSocket* /* qSocket */,
+ const InstrumentationObserver::PMTUBlackholeEvent& event) {
+ EXPECT_LE(d6d.meta.timeLastNonSearchState, event.blackholeTime);
+ EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
+ EXPECT_EQ(D6DMachineState::SEARCHING, event.currentState);
+ EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
+ EXPECT_EQ(d6d.basePMTU + 30, event.lastProbeSize);
+ EXPECT_EQ(0, event.blackholeDetectionWindow);
+ EXPECT_EQ(1, event.blackholeDetectionThreshold);
+ EXPECT_EQ(
+ d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
+ }));
+
+ detectPMTUBlackhole(conn, lostPacket);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
+
+ EXPECT_EQ(d6d.state, D6DMachineState::BASE);
+ EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
+ EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
+ EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
+}
+
+TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearchComplete) {
+ QuicConnectionStateBase conn(QuicNodeType::Server);
+ auto& d6d = conn.d6d;
+ auto now = Clock::now();
+ auto mockInstrumentationObserver =
+ std::make_unique<StrictMock<MockInstrumentationObserver>>();
+ conn.instrumentationObservers_.push_back(mockInstrumentationObserver.get());
+ d6d.state = D6DMachineState::SEARCH_COMPLETE;
+ conn.udpSendPacketLen = d6d.basePMTU + 20;
+ d6d.currentProbeSize = d6d.basePMTU + 20;
+ d6d.meta.lastNonSearchState = D6DMachineState::BASE;
+ d6d.meta.timeLastNonSearchState = now;
+ auto pkt = OutstandingPacket(
+ makeTestShortPacket(),
+ now + 10s,
+ d6d.currentProbeSize,
+ false,
+ true,
+ d6d.currentProbeSize,
+ d6d.currentProbeSize);
+ d6d.lastProbe = D6DProbePacket(
+ pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
+
+ auto lostPacket = OutstandingPacket(
+ makeTestShortPacket(),
+ now + 12s,
+ conn.udpSendPacketLen,
+ false,
+ conn.udpSendPacketLen + d6d.currentProbeSize,
+ conn.udpSendPacketLen + d6d.currentProbeSize);
+
+ d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
+ std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
+ 1); // Threshold of 1 will cause window to be set to 0
+
+ EXPECT_CALL(*mockInstrumentationObserver, pmtuBlackholeDetected(_, _))
+ .Times(1)
+ .WillOnce(
+ Invoke([&](QuicSocket* /* qSocket */,
+ const InstrumentationObserver::PMTUBlackholeEvent& event) {
+ EXPECT_EQ(d6d.meta.timeLastNonSearchState, event.blackholeTime);
+ EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
+ EXPECT_EQ(D6DMachineState::SEARCH_COMPLETE, event.currentState);
+ EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
+ EXPECT_EQ(d6d.basePMTU + 20, event.lastProbeSize);
+ EXPECT_EQ(0, event.blackholeDetectionWindow);
+ EXPECT_EQ(1, event.blackholeDetectionThreshold);
+ EXPECT_EQ(
+ d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
+ }));
+
+ detectPMTUBlackhole(conn, lostPacket);
+ for (auto& callback : conn.pendingCallbacks) {
+ callback(nullptr);
+ }
+
EXPECT_EQ(d6d.state, D6DMachineState::BASE);
EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
+ EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::SEARCH_COMPLETE);
+ EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
}
} // namespace test

Add instrumentation callback for pmtu blackhole and upperbound

Summary:
This adds two events that we care mostly about d6d's perf: detecting blackhole
and detecting upperbound.

Reviewed By: bschlinker

Differential Revision: D23972141

fbshipit-source-id: bb6db6d1548ecddc9b255149e04a4c4844e9ec54