| OLD | NEW |
| 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/quic/core/congestion_control/cubic_bytes.h" | 5 #include "net/quic/core/congestion_control/cubic_bytes.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "net/quic/core/quic_flags.h" | 8 #include "net/quic/core/quic_flags.h" |
| 9 #include "net/quic/test_tools/mock_clock.h" | 9 #include "net/quic/test_tools/mock_clock.h" |
| 10 #include "testing/gtest/include/gtest/gtest.h" | 10 #include "testing/gtest/include/gtest/gtest.h" |
| 11 | 11 |
| 12 namespace net { | 12 namespace net { |
| 13 namespace test { | 13 namespace test { |
| 14 | 14 |
| 15 const float kBeta = 0.7f; // Default Cubic backoff factor. | 15 const float kBeta = 0.7f; // Default Cubic backoff factor. |
| 16 const uint32_t kNumConnections = 2; | 16 const uint32_t kNumConnections = 2; |
| 17 const float kNConnectionBeta = (kNumConnections - 1 + kBeta) / kNumConnections; | 17 const float kNConnectionBeta = (kNumConnections - 1 + kBeta) / kNumConnections; |
| 18 const float kNConnectionAlpha = 3 * kNumConnections * kNumConnections * | 18 const float kNConnectionAlpha = 3 * kNumConnections * kNumConnections * |
| 19 (1 - kNConnectionBeta) / (1 + kNConnectionBeta); | 19 (1 - kNConnectionBeta) / (1 + kNConnectionBeta); |
| 20 | 20 |
| 21 class CubicBytesTest : public ::testing::Test { | 21 class CubicBytesTest : public ::testing::TestWithParam<bool> { |
| 22 protected: | 22 protected: |
| 23 CubicBytesTest() | 23 CubicBytesTest() |
| 24 : one_ms_(QuicTime::Delta::FromMilliseconds(1)), | 24 : one_ms_(QuicTime::Delta::FromMilliseconds(1)), |
| 25 hundred_ms_(QuicTime::Delta::FromMilliseconds(100)), | 25 hundred_ms_(QuicTime::Delta::FromMilliseconds(100)), |
| 26 cubic_(&clock_) {} | 26 cubic_(&clock_), |
| 27 fix_convex_mode_(GetParam()) { |
| 28 cubic_.SetFixConvexMode(fix_convex_mode_); |
| 29 } |
| 30 |
| 31 QuicByteCount RenoCwndInBytes(QuicByteCount current_cwnd) { |
| 32 QuicByteCount reno_estimated_cwnd = |
| 33 current_cwnd + |
| 34 kDefaultTCPMSS * (kNConnectionAlpha * kDefaultTCPMSS) / current_cwnd; |
| 35 return reno_estimated_cwnd; |
| 36 } |
| 37 |
| 38 QuicByteCount ConservativeCwndInBytes(QuicByteCount current_cwnd) { |
| 39 QuicByteCount conservative_cwnd = current_cwnd + kDefaultTCPMSS / 2; |
| 40 return conservative_cwnd; |
| 41 } |
| 42 |
| 43 QuicByteCount CubicConvexCwndInBytes(QuicByteCount initial_cwnd, |
| 44 int64_t rtt_ms, |
| 45 int64_t elapsed_time_ms) { |
| 46 const int64_t offset = ((elapsed_time_ms + rtt_ms) << 10) / 1000; |
| 47 const QuicByteCount delta_congestion_window = |
| 48 ((410 * offset * offset * offset) >> 40) * kDefaultTCPMSS; |
| 49 const QuicByteCount cubic_cwnd = initial_cwnd + delta_congestion_window; |
| 50 return cubic_cwnd; |
| 51 } |
| 52 |
| 27 const QuicTime::Delta one_ms_; | 53 const QuicTime::Delta one_ms_; |
| 28 const QuicTime::Delta hundred_ms_; | 54 const QuicTime::Delta hundred_ms_; |
| 29 MockClock clock_; | 55 MockClock clock_; |
| 30 CubicBytes cubic_; | 56 CubicBytes cubic_; |
| 57 bool fix_convex_mode_; |
| 31 }; | 58 }; |
| 32 | 59 |
| 33 TEST_F(CubicBytesTest, AboveOrigin) { | 60 INSTANTIATE_TEST_CASE_P(CubicBytesTests, CubicBytesTest, testing::Bool()); |
| 61 |
| 62 // TODO(jokulik): The original "AboveOrigin" test, below, is very |
| 63 // loose. It's nearly impossible to make the test tighter without |
| 64 // deploying the fix for convex mode. Once cubic convex is deployed, |
| 65 // replace "AboveOrigin" with this test. |
| 66 TEST_P(CubicBytesTest, AboveOriginWithTighterBounds) { |
| 67 if (!fix_convex_mode_) { |
| 68 // Without convex mode fixed, the behavior of the algorithm is so |
| 69 // far from expected, there's no point in doing a tighter test. |
| 70 return; |
| 71 } |
| 72 // Convex growth. |
| 73 const QuicTime::Delta rtt_min = hundred_ms_; |
| 74 int64_t rtt_min_ms = rtt_min.ToMilliseconds(); |
| 75 float rtt_min_s = rtt_min_ms / 1000.0; |
| 76 QuicByteCount current_cwnd = 10 * kDefaultTCPMSS; |
| 77 const QuicByteCount initial_cwnd = current_cwnd; |
| 78 |
| 79 clock_.AdvanceTime(one_ms_); |
| 80 const QuicTime initial_time = clock_.ApproximateNow(); |
| 81 const QuicByteCount expected_first_cwnd = RenoCwndInBytes(current_cwnd); |
| 82 current_cwnd = |
| 83 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 84 ASSERT_EQ(expected_first_cwnd, current_cwnd); |
| 85 |
| 86 // Normal TCP phase. |
| 87 // The maximum number of expected Reno RTTs is calculated by |
| 88 // finding the point where the cubic curve and the reno curve meet. |
| 89 const int max_reno_rtts = |
| 90 std::sqrt(kNConnectionAlpha / (.4 * rtt_min_s * rtt_min_s * rtt_min_s)) - |
| 91 1; |
| 92 for (int i = 0; i < max_reno_rtts; ++i) { |
| 93 // Alternatively, we expect it to increase by one, every time we |
| 94 // receive current_cwnd/Alpha acks back. (This is another way of |
| 95 // saying we expect cwnd to increase by approximately Alpha once |
| 96 // we receive current_cwnd number ofacks back). |
| 97 const uint64_t num_acks_this_epoch = |
| 98 current_cwnd / kDefaultTCPMSS / kNConnectionAlpha; |
| 99 const QuicByteCount initial_cwnd_this_epoch = current_cwnd; |
| 100 for (QuicPacketCount n = 0; n < num_acks_this_epoch; ++n) { |
| 101 // Call once per ACK. |
| 102 const QuicByteCount expected_next_cwnd = RenoCwndInBytes(current_cwnd); |
| 103 current_cwnd = cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, |
| 104 current_cwnd, rtt_min); |
| 105 ASSERT_EQ(expected_next_cwnd, current_cwnd); |
| 106 } |
| 107 // Our byte-wise Reno implementation is an estimate. We expect |
| 108 // the cwnd to increase by approximately one MSS every |
| 109 // cwnd/kDefaultTCPMSS/Alpha acks, but it may be off by as much as |
| 110 // half a packet for smaller values of current_cwnd. |
| 111 const QuicByteCount cwnd_change_this_epoch = |
| 112 current_cwnd - initial_cwnd_this_epoch; |
| 113 ASSERT_NEAR(kDefaultTCPMSS, cwnd_change_this_epoch, kDefaultTCPMSS / 2); |
| 114 clock_.AdvanceTime(hundred_ms_); |
| 115 } |
| 116 |
| 117 // Because our byte-wise Reno under-estimates the cwnd, we switch to |
| 118 // conservative increases for a few acks before switching to true |
| 119 // cubic increases. |
| 120 for (int i = 0; i < 3; ++i) { |
| 121 const QuicByteCount next_expected_cwnd = |
| 122 ConservativeCwndInBytes(current_cwnd); |
| 123 current_cwnd = |
| 124 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 125 ASSERT_EQ(next_expected_cwnd, current_cwnd); |
| 126 } |
| 127 |
| 128 for (int i = 0; i < 54; ++i) { |
| 129 const uint64_t max_acks_this_epoch = current_cwnd / kDefaultTCPMSS; |
| 130 const int elapsed_time_ms = |
| 131 (clock_.ApproximateNow() - initial_time).ToMilliseconds(); |
| 132 const QuicByteCount expected_cwnd = CubicConvexCwndInBytes( |
| 133 initial_cwnd, rtt_min.ToMilliseconds(), elapsed_time_ms); |
| 134 current_cwnd = |
| 135 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 136 ASSERT_EQ(expected_cwnd, current_cwnd); |
| 137 |
| 138 for (QuicPacketCount n = 1; n < max_acks_this_epoch; ++n) { |
| 139 // Call once per ACK. |
| 140 ASSERT_EQ(current_cwnd, cubic_.CongestionWindowAfterAck( |
| 141 kDefaultTCPMSS, current_cwnd, rtt_min)); |
| 142 } |
| 143 clock_.AdvanceTime(hundred_ms_); |
| 144 } |
| 145 const int elapsed_time_ms = |
| 146 (clock_.ApproximateNow() - initial_time).ToMilliseconds(); |
| 147 const QuicByteCount expected_cwnd = CubicConvexCwndInBytes( |
| 148 initial_cwnd, rtt_min.ToMilliseconds(), elapsed_time_ms); |
| 149 current_cwnd = |
| 150 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 151 ASSERT_EQ(expected_cwnd, current_cwnd); |
| 152 } |
| 153 |
| 154 TEST_P(CubicBytesTest, AboveOrigin) { |
| 34 // Convex growth. | 155 // Convex growth. |
| 35 const QuicTime::Delta rtt_min = hundred_ms_; | 156 const QuicTime::Delta rtt_min = hundred_ms_; |
| 36 QuicByteCount current_cwnd = 10 * kDefaultTCPMSS; | 157 QuicByteCount current_cwnd = 10 * kDefaultTCPMSS; |
| 37 QuicByteCount expected_cwnd = current_cwnd + (kDefaultTCPMSS / 2); | 158 // Without the signed-integer, cubic-convex fix, we start out in the |
| 159 // wrong mode. |
| 160 QuicPacketCount expected_cwnd = fix_convex_mode_ |
| 161 ? RenoCwndInBytes(current_cwnd) |
| 162 : ConservativeCwndInBytes(current_cwnd); |
| 38 // Initialize the state. | 163 // Initialize the state. |
| 39 clock_.AdvanceTime(one_ms_); | 164 clock_.AdvanceTime(one_ms_); |
| 40 EXPECT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( | 165 ASSERT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( |
| 41 kDefaultTCPMSS, current_cwnd, rtt_min)); | 166 kDefaultTCPMSS, current_cwnd, rtt_min)); |
| 42 current_cwnd = expected_cwnd; | 167 current_cwnd = expected_cwnd; |
| 168 const QuicPacketCount initial_cwnd = expected_cwnd; |
| 43 // Normal TCP phase. | 169 // Normal TCP phase. |
| 44 for (int i = 0; i < 48; ++i) { | 170 for (int i = 0; i < 48; ++i) { |
| 45 for (QuicPacketCount n = 1; | 171 for (QuicPacketCount n = 1; |
| 46 n < current_cwnd / kDefaultTCPMSS / kNConnectionAlpha; ++n) { | 172 n < current_cwnd / kDefaultTCPMSS / kNConnectionAlpha; ++n) { |
| 47 // Call once per ACK. | 173 // Call once per ACK. |
| 48 EXPECT_NEAR(current_cwnd, cubic_.CongestionWindowAfterAck( | 174 ASSERT_NEAR(current_cwnd, cubic_.CongestionWindowAfterAck( |
| 49 kDefaultTCPMSS, current_cwnd, rtt_min), | 175 kDefaultTCPMSS, current_cwnd, rtt_min), |
| 50 kDefaultTCPMSS); | 176 kDefaultTCPMSS); |
| 51 } | 177 } |
| 52 clock_.AdvanceTime(hundred_ms_); | 178 clock_.AdvanceTime(hundred_ms_); |
| 53 current_cwnd = | 179 current_cwnd = |
| 54 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); | 180 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 55 EXPECT_NEAR(expected_cwnd, current_cwnd, kDefaultTCPMSS); | 181 if (fix_convex_mode_) { |
| 56 expected_cwnd += kDefaultTCPMSS; | 182 // When we fix convex mode and the uint64 arithmetic, we |
| 183 // increase the expected_cwnd only after after the first 100ms, |
| 184 // rather than after the initial 1ms. |
| 185 expected_cwnd += kDefaultTCPMSS; |
| 186 ASSERT_NEAR(expected_cwnd, current_cwnd, kDefaultTCPMSS); |
| 187 } else { |
| 188 ASSERT_NEAR(expected_cwnd, current_cwnd, kDefaultTCPMSS); |
| 189 expected_cwnd += kDefaultTCPMSS; |
| 190 } |
| 57 } | 191 } |
| 58 // Cubic phase. | 192 // Cubic phase. |
| 59 for (int i = 0; i < 52; ++i) { | 193 for (int i = 0; i < 52; ++i) { |
| 60 for (QuicPacketCount n = 1; n < current_cwnd / kDefaultTCPMSS; ++n) { | 194 for (QuicPacketCount n = 1; n < current_cwnd / kDefaultTCPMSS; ++n) { |
| 61 // Call once per ACK. | 195 // Call once per ACK. |
| 62 EXPECT_NEAR(current_cwnd, cubic_.CongestionWindowAfterAck( | 196 ASSERT_NEAR(current_cwnd, cubic_.CongestionWindowAfterAck( |
| 63 kDefaultTCPMSS, current_cwnd, rtt_min), | 197 kDefaultTCPMSS, current_cwnd, rtt_min), |
| 64 kDefaultTCPMSS); | 198 kDefaultTCPMSS); |
| 65 } | 199 } |
| 66 clock_.AdvanceTime(hundred_ms_); | 200 clock_.AdvanceTime(hundred_ms_); |
| 67 current_cwnd = | 201 current_cwnd = |
| 68 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); | 202 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 69 } | 203 } |
| 70 // Total time elapsed so far; add min_rtt (0.1s) here as well. | 204 // Total time elapsed so far; add min_rtt (0.1s) here as well. |
| 71 float elapsed_time_s = 10.0f + 0.1f; | 205 float elapsed_time_s = 10.0f + 0.1f; |
| 72 // |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4. | 206 // |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4. |
| 73 expected_cwnd = | 207 expected_cwnd = |
| 74 11 + (elapsed_time_s * elapsed_time_s * elapsed_time_s * 410) / 1024; | 208 initial_cwnd / kDefaultTCPMSS + |
| 209 (elapsed_time_s * elapsed_time_s * elapsed_time_s * 410) / 1024; |
| 210 // Without the convex mode fix, the result is off by one. |
| 211 if (!fix_convex_mode_) { |
| 212 ++expected_cwnd; |
| 213 } |
| 75 EXPECT_EQ(expected_cwnd, current_cwnd / kDefaultTCPMSS); | 214 EXPECT_EQ(expected_cwnd, current_cwnd / kDefaultTCPMSS); |
| 76 } | 215 } |
| 77 | 216 |
| 78 TEST_F(CubicBytesTest, LossEvents) { | 217 TEST_P(CubicBytesTest, LossEvents) { |
| 79 const QuicTime::Delta rtt_min = hundred_ms_; | 218 const QuicTime::Delta rtt_min = hundred_ms_; |
| 80 QuicByteCount current_cwnd = 422 * kDefaultTCPMSS; | 219 QuicByteCount current_cwnd = 422 * kDefaultTCPMSS; |
| 81 QuicPacketCount expected_cwnd = current_cwnd + kDefaultTCPMSS / 2; | 220 // Without the signed-integer, cubic-convex fix, we mistakenly |
| 221 // increment cwnd after only one_ms_ and a single ack. |
| 222 QuicPacketCount expected_cwnd = fix_convex_mode_ |
| 223 ? RenoCwndInBytes(current_cwnd) |
| 224 : current_cwnd + kDefaultTCPMSS / 2; |
| 82 // Initialize the state. | 225 // Initialize the state. |
| 83 clock_.AdvanceTime(one_ms_); | 226 clock_.AdvanceTime(one_ms_); |
| 84 EXPECT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( | 227 EXPECT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( |
| 85 kDefaultTCPMSS, current_cwnd, rtt_min)); | 228 kDefaultTCPMSS, current_cwnd, rtt_min)); |
| 86 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); | 229 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); |
| 87 EXPECT_EQ(expected_cwnd, | 230 EXPECT_EQ(expected_cwnd, |
| 88 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); | 231 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); |
| 89 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); | 232 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); |
| 90 EXPECT_EQ(expected_cwnd, | 233 EXPECT_EQ(expected_cwnd, |
| 91 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); | 234 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); |
| 92 } | 235 } |
| 93 | 236 |
| 94 TEST_F(CubicBytesTest, BelowOrigin) { | 237 TEST_P(CubicBytesTest, BelowOrigin) { |
| 95 // Concave growth. | 238 // Concave growth. |
| 96 const QuicTime::Delta rtt_min = hundred_ms_; | 239 const QuicTime::Delta rtt_min = hundred_ms_; |
| 97 QuicByteCount current_cwnd = 422 * kDefaultTCPMSS; | 240 QuicByteCount current_cwnd = 422 * kDefaultTCPMSS; |
| 98 QuicPacketCount expected_cwnd = current_cwnd + kDefaultTCPMSS / 2; | 241 // Without the signed-integer, cubic-convex fix, we mistakenly |
| 242 // increment cwnd after only one_ms_ and a single ack. |
| 243 QuicPacketCount expected_cwnd = fix_convex_mode_ |
| 244 ? RenoCwndInBytes(current_cwnd) |
| 245 : current_cwnd + kDefaultTCPMSS / 2; |
| 99 // Initialize the state. | 246 // Initialize the state. |
| 100 clock_.AdvanceTime(one_ms_); | 247 clock_.AdvanceTime(one_ms_); |
| 101 EXPECT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( | 248 EXPECT_EQ(expected_cwnd, cubic_.CongestionWindowAfterAck( |
| 102 kDefaultTCPMSS, current_cwnd, rtt_min)); | 249 kDefaultTCPMSS, current_cwnd, rtt_min)); |
| 103 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); | 250 expected_cwnd = static_cast<QuicPacketCount>(current_cwnd * kNConnectionBeta); |
| 104 EXPECT_EQ(expected_cwnd, | 251 EXPECT_EQ(expected_cwnd, |
| 105 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); | 252 cubic_.CongestionWindowAfterPacketLoss(current_cwnd)); |
| 106 current_cwnd = expected_cwnd; | 253 current_cwnd = expected_cwnd; |
| 107 // First update after loss to initialize the epoch. | 254 // First update after loss to initialize the epoch. |
| 108 current_cwnd = | 255 current_cwnd = |
| 109 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); | 256 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 110 // Cubic phase. | 257 // Cubic phase. |
| 111 for (int i = 0; i < 40; ++i) { | 258 for (int i = 0; i < 40; ++i) { |
| 112 clock_.AdvanceTime(hundred_ms_); | 259 clock_.AdvanceTime(hundred_ms_); |
| 113 current_cwnd = | 260 current_cwnd = |
| 114 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); | 261 cubic_.CongestionWindowAfterAck(kDefaultTCPMSS, current_cwnd, rtt_min); |
| 115 } | 262 } |
| 116 expected_cwnd = 553632; | 263 expected_cwnd = 553632; |
| 117 EXPECT_EQ(expected_cwnd, current_cwnd); | 264 EXPECT_EQ(expected_cwnd, current_cwnd); |
| 118 } | 265 } |
| 119 | 266 |
| 120 } // namespace test | 267 } // namespace test |
| 121 } // namespace net | 268 } // namespace net |
| OLD | NEW |