Adding RttStats class for BBR.
This is part of a series of CLs adding a network controller based on the BBR congestion control method. The code is based on the QUIC BBR implementation in Chromium. Bug: webrtc:8415 Change-Id: Ib952b9c3f1cdd0741330a5ae95aebcf2488f7f1d Reviewed-on: https://webrtc-review.googlesource.com/63644 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22581}
This commit is contained in:
parent
06e363a6e6
commit
9bd1af199b
@ -183,6 +183,7 @@ if (rtc_include_tests) {
|
||||
"../pacing:pacing",
|
||||
"../remote_bitrate_estimator:remote_bitrate_estimator",
|
||||
"../rtp_rtcp:rtp_rtcp_format",
|
||||
"bbr:bbr_unittests",
|
||||
"goog_cc:goog_cc_unittests",
|
||||
"network_control:network_control_unittests",
|
||||
"rtp:congestion_controller_unittests",
|
||||
|
||||
34
modules/congestion_controller/bbr/BUILD.gn
Normal file
34
modules/congestion_controller/bbr/BUILD.gn
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style license
|
||||
# that can be found in the LICENSE file in the root of the source
|
||||
# tree. An additional intellectual property rights grant can be found
|
||||
# in the file PATENTS. All contributing project authors may
|
||||
# be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
import("../../../webrtc.gni")
|
||||
|
||||
rtc_source_set("rtt_stats") {
|
||||
sources = [
|
||||
"rtt_stats.cc",
|
||||
"rtt_stats.h",
|
||||
]
|
||||
deps = [
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../network_control",
|
||||
]
|
||||
}
|
||||
|
||||
if (rtc_include_tests) {
|
||||
rtc_source_set("bbr_unittests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"rtt_stats_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":rtt_stats",
|
||||
"../../../test:test_support",
|
||||
"../network_control",
|
||||
]
|
||||
}
|
||||
}
|
||||
96
modules/congestion_controller/bbr/rtt_stats.cc
Normal file
96
modules/congestion_controller/bbr/rtt_stats.cc
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "modules/congestion_controller/network_control/include/network_units.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace {
|
||||
|
||||
// Default initial rtt used before any samples are received.
|
||||
const int kInitialRttMs = 100;
|
||||
const double kAlpha = 0.125;
|
||||
const double kOneMinusAlpha = (1 - kAlpha);
|
||||
const double kBeta = 0.25;
|
||||
const double kOneMinusBeta = (1 - kBeta);
|
||||
const int64_t kNumMicrosPerMilli = 1000;
|
||||
} // namespace
|
||||
|
||||
RttStats::RttStats()
|
||||
: latest_rtt_(TimeDelta::Zero()),
|
||||
min_rtt_(TimeDelta::Zero()),
|
||||
smoothed_rtt_(TimeDelta::Zero()),
|
||||
previous_srtt_(TimeDelta::Zero()),
|
||||
mean_deviation_(TimeDelta::Zero()),
|
||||
initial_rtt_us_(kInitialRttMs * kNumMicrosPerMilli) {}
|
||||
|
||||
void RttStats::ExpireSmoothedMetrics() {
|
||||
mean_deviation_ =
|
||||
std::max(mean_deviation_, (smoothed_rtt_ - latest_rtt_).Abs());
|
||||
smoothed_rtt_ = std::max(smoothed_rtt_, latest_rtt_);
|
||||
}
|
||||
|
||||
// Updates the RTT based on a new sample.
|
||||
void RttStats::UpdateRtt(TimeDelta send_delta,
|
||||
TimeDelta ack_delay,
|
||||
Timestamp now) {
|
||||
if (send_delta.IsInfinite() || send_delta <= TimeDelta::Zero()) {
|
||||
RTC_LOG(LS_WARNING) << "Ignoring measured send_delta, because it's is "
|
||||
<< "either infinite, zero, or negative. send_delta = "
|
||||
<< send_delta;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update min_rtt_ first. min_rtt_ does not use an rtt_sample corrected for
|
||||
// ack_delay but the raw observed send_delta, since poor clock granularity at
|
||||
// the client may cause a high ack_delay to result in underestimation of the
|
||||
// min_rtt_.
|
||||
if (min_rtt_.IsZero() || min_rtt_ > send_delta) {
|
||||
min_rtt_ = send_delta;
|
||||
}
|
||||
|
||||
// Correct for ack_delay if information received from the peer results in a
|
||||
// positive RTT sample. Otherwise, we use the send_delta as a reasonable
|
||||
// measure for smoothed_rtt.
|
||||
TimeDelta rtt_sample = send_delta;
|
||||
previous_srtt_ = smoothed_rtt_;
|
||||
|
||||
if (rtt_sample > ack_delay) {
|
||||
rtt_sample = rtt_sample - ack_delay;
|
||||
}
|
||||
latest_rtt_ = rtt_sample;
|
||||
// First time call.
|
||||
if (smoothed_rtt_.IsZero()) {
|
||||
smoothed_rtt_ = rtt_sample;
|
||||
mean_deviation_ = rtt_sample / 2;
|
||||
} else {
|
||||
mean_deviation_ = kOneMinusBeta * mean_deviation_ +
|
||||
kBeta * (smoothed_rtt_ - rtt_sample).Abs();
|
||||
smoothed_rtt_ = kOneMinusAlpha * smoothed_rtt_ + kAlpha * rtt_sample;
|
||||
RTC_LOG(LS_VERBOSE) << " smoothed_rtt(us):" << smoothed_rtt_.us()
|
||||
<< " mean_deviation(us):" << mean_deviation_.us();
|
||||
}
|
||||
}
|
||||
|
||||
void RttStats::OnConnectionMigration() {
|
||||
latest_rtt_ = TimeDelta::Zero();
|
||||
min_rtt_ = TimeDelta::Zero();
|
||||
smoothed_rtt_ = TimeDelta::Zero();
|
||||
mean_deviation_ = TimeDelta::Zero();
|
||||
initial_rtt_us_ = kInitialRttMs * kNumMicrosPerMilli;
|
||||
}
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
88
modules/congestion_controller/bbr/rtt_stats.h
Normal file
88
modules/congestion_controller/bbr/rtt_stats.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
// A convenience class to store RTT samples and calculate smoothed RTT.
|
||||
// From the Quic BBR implementation in Chromium.
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "modules/congestion_controller/network_control/include/network_units.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
class RttStats {
|
||||
public:
|
||||
RttStats();
|
||||
|
||||
// Updates the RTT from an incoming ack which is received |send_delta| after
|
||||
// the packet is sent and the peer reports the ack being delayed |ack_delay|.
|
||||
void UpdateRtt(TimeDelta send_delta, TimeDelta ack_delay, Timestamp now);
|
||||
|
||||
// Causes the smoothed_rtt to be increased to the latest_rtt if the latest_rtt
|
||||
// is larger. The mean deviation is increased to the most recent deviation if
|
||||
// it's larger.
|
||||
void ExpireSmoothedMetrics();
|
||||
|
||||
// Called when connection migrates and RTT measurement needs to be reset.
|
||||
void OnConnectionMigration();
|
||||
|
||||
// Returns the EWMA smoothed RTT for the connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta smoothed_rtt() const { return smoothed_rtt_; }
|
||||
|
||||
// Returns the EWMA smoothed RTT prior to the most recent RTT sample.
|
||||
TimeDelta previous_srtt() const { return previous_srtt_; }
|
||||
|
||||
int64_t initial_rtt_us() const { return initial_rtt_us_; }
|
||||
|
||||
// Sets an initial RTT to be used for SmoothedRtt before any RTT updates.
|
||||
void set_initial_rtt_us(int64_t initial_rtt_us) {
|
||||
RTC_DCHECK_GE(initial_rtt_us, 0);
|
||||
if (initial_rtt_us <= 0) {
|
||||
RTC_LOG(LS_ERROR) << "Attempt to set initial rtt to <= 0.";
|
||||
return;
|
||||
}
|
||||
initial_rtt_us_ = initial_rtt_us;
|
||||
}
|
||||
|
||||
// The most recent RTT measurement.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta latest_rtt() const { return latest_rtt_; }
|
||||
|
||||
// Returns the min_rtt for the entire connection.
|
||||
// May return Zero if no valid updates have occurred.
|
||||
TimeDelta min_rtt() const { return min_rtt_; }
|
||||
|
||||
TimeDelta mean_deviation() const { return mean_deviation_; }
|
||||
|
||||
private:
|
||||
TimeDelta latest_rtt_;
|
||||
TimeDelta min_rtt_;
|
||||
TimeDelta smoothed_rtt_;
|
||||
TimeDelta previous_srtt_;
|
||||
// Mean RTT deviation during this session.
|
||||
// Approximation of standard deviation, the error is roughly 1.25 times
|
||||
// larger than the standard deviation, for a normally distributed signal.
|
||||
TimeDelta mean_deviation_;
|
||||
int64_t initial_rtt_us_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(RttStats);
|
||||
};
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_RTT_STATS_H_
|
||||
160
modules/congestion_controller/bbr/rtt_stats_unittest.cc
Normal file
160
modules/congestion_controller/bbr/rtt_stats_unittest.cc
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace test {
|
||||
|
||||
class RttStatsTest : public ::testing::Test {
|
||||
protected:
|
||||
RttStats rtt_stats_;
|
||||
};
|
||||
|
||||
TEST_F(RttStatsTest, DefaultsBeforeUpdate) {
|
||||
EXPECT_LT(0u, rtt_stats_.initial_rtt_us());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, SmoothedRtt) {
|
||||
// Verify that ack_delay is corrected for in Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(300), TimeDelta::ms(100),
|
||||
Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.smoothed_rtt());
|
||||
// Verify that effective RTT of zero does not change Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(200), TimeDelta::ms(200),
|
||||
Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.smoothed_rtt());
|
||||
// Verify that large erroneous ack_delay does not change Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(200), TimeDelta::ms(300),
|
||||
Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
|
||||
// Ensure that the potential rounding artifacts in EWMA calculation do not cause
|
||||
// the SRTT to drift too far from the exact value.
|
||||
TEST_F(RttStatsTest, SmoothedRttStability) {
|
||||
for (int64_t time = 3; time < 20000; time++) {
|
||||
RttStats stats;
|
||||
for (int64_t i = 0; i < 100; i++) {
|
||||
stats.UpdateRtt(TimeDelta::us(time), TimeDelta::ms(0), Timestamp::ms(0));
|
||||
int64_t time_delta_us = stats.smoothed_rtt().us() - time;
|
||||
ASSERT_LE(std::abs(time_delta_us), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, PreviousSmoothedRtt) {
|
||||
// Verify that ack_delay is corrected for in Smoothed RTT.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(300), TimeDelta::ms(100),
|
||||
Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.previous_srtt());
|
||||
// Ensure the previous SRTT is 200ms after a 100ms sample.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(100), TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(100), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::us(187500).us(), rtt_stats_.smoothed_rtt().us());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.previous_srtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, MinRtt) {
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(200), TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(10), TimeDelta::Zero(),
|
||||
Timestamp::ms(0) + TimeDelta::ms(10));
|
||||
EXPECT_EQ(TimeDelta::ms(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(50), TimeDelta::Zero(),
|
||||
Timestamp::ms(0) + TimeDelta::ms(20));
|
||||
EXPECT_EQ(TimeDelta::ms(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(50), TimeDelta::Zero(),
|
||||
Timestamp::ms(0) + TimeDelta::ms(30));
|
||||
EXPECT_EQ(TimeDelta::ms(10), rtt_stats_.min_rtt());
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(50), TimeDelta::Zero(),
|
||||
Timestamp::ms(0) + TimeDelta::ms(40));
|
||||
EXPECT_EQ(TimeDelta::ms(10), rtt_stats_.min_rtt());
|
||||
// Verify that ack_delay does not go into recording of min_rtt_.
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(7), TimeDelta::ms(2),
|
||||
Timestamp::ms(0) + TimeDelta::ms(50));
|
||||
EXPECT_EQ(TimeDelta::ms(7), rtt_stats_.min_rtt());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, ExpireSmoothedMetrics) {
|
||||
TimeDelta initial_rtt = TimeDelta::ms(10);
|
||||
rtt_stats_.UpdateRtt(initial_rtt, TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
EXPECT_EQ(0.5 * initial_rtt, rtt_stats_.mean_deviation());
|
||||
|
||||
// Update once with a 20ms RTT.
|
||||
TimeDelta doubled_rtt = 2 * initial_rtt;
|
||||
rtt_stats_.UpdateRtt(doubled_rtt, TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(1.125 * initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
// Expire the smoothed metrics, increasing smoothed rtt and mean deviation.
|
||||
rtt_stats_.ExpireSmoothedMetrics();
|
||||
EXPECT_EQ(doubled_rtt, rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(0.875 * initial_rtt, rtt_stats_.mean_deviation());
|
||||
|
||||
// Now go back down to 5ms and expire the smoothed metrics, and ensure the
|
||||
// mean deviation increases to 15ms.
|
||||
TimeDelta half_rtt = 0.5 * initial_rtt;
|
||||
rtt_stats_.UpdateRtt(half_rtt, TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_GT(doubled_rtt, rtt_stats_.smoothed_rtt());
|
||||
EXPECT_LT(initial_rtt, rtt_stats_.mean_deviation());
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, UpdateRttWithBadSendDeltas) {
|
||||
// Make sure we ignore bad RTTs.
|
||||
|
||||
TimeDelta initial_rtt = TimeDelta::ms(10);
|
||||
rtt_stats_.UpdateRtt(initial_rtt, TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
|
||||
std::vector<TimeDelta> bad_send_deltas;
|
||||
bad_send_deltas.push_back(TimeDelta::Zero());
|
||||
bad_send_deltas.push_back(TimeDelta::Infinity());
|
||||
bad_send_deltas.push_back(TimeDelta::us(-1000));
|
||||
|
||||
for (TimeDelta bad_send_delta : bad_send_deltas) {
|
||||
rtt_stats_.UpdateRtt(bad_send_delta, TimeDelta::Zero(), Timestamp::ms(0));
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
|
||||
EXPECT_EQ(initial_rtt, rtt_stats_.smoothed_rtt());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RttStatsTest, ResetAfterConnectionMigrations) {
|
||||
rtt_stats_.UpdateRtt(TimeDelta::ms(300), TimeDelta::ms(100),
|
||||
Timestamp::ms(0));
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(200), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::ms(300), rtt_stats_.min_rtt());
|
||||
|
||||
// Reset rtt stats on connection migrations.
|
||||
rtt_stats_.OnConnectionMigration();
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.latest_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.smoothed_rtt());
|
||||
EXPECT_EQ(TimeDelta::Zero(), rtt_stats_.min_rtt());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
@ -91,6 +91,9 @@ class TimeDelta {
|
||||
TimeDelta operator*(int32_t scalar) const {
|
||||
return TimeDelta::us(us() * scalar);
|
||||
}
|
||||
TimeDelta operator/(int64_t scalar) const {
|
||||
return TimeDelta::us(us() / scalar);
|
||||
}
|
||||
bool operator==(const TimeDelta& other) const {
|
||||
return microseconds_ == other.microseconds_;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user