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:
Sebastian Jansson 2018-03-22 15:34:01 +01:00 committed by Commit Bot
parent 06e363a6e6
commit 9bd1af199b
6 changed files with 382 additions and 0 deletions

View File

@ -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",

View 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",
]
}
}

View 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

View 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_

View 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

View File

@ -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_;
}