Adding WindowedFilter 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: I4478e8d5e2abd361b0d22de00ebe04bde9ef18f6 Reviewed-on: https://webrtc-review.googlesource.com/63681 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22592}
This commit is contained in:
committed by
Commit Bot
parent
0bf0c3d7aa
commit
b0ba558c96
@ -29,16 +29,23 @@ rtc_source_set("rtt_stats") {
|
||||
"../network_control",
|
||||
]
|
||||
}
|
||||
rtc_source_set("windowed_filter") {
|
||||
sources = [
|
||||
"windowed_filter.h",
|
||||
]
|
||||
}
|
||||
if (rtc_include_tests) {
|
||||
rtc_source_set("bbr_unittests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"data_transfer_tracker_unittest.cc",
|
||||
"rtt_stats_unittest.cc",
|
||||
"windowed_filter_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":data_transfer_tracker",
|
||||
":rtt_stats",
|
||||
":windowed_filter",
|
||||
"../../../test:test_support",
|
||||
"../network_control",
|
||||
]
|
||||
|
||||
168
modules/congestion_controller/bbr/windowed_filter.h
Normal file
168
modules/congestion_controller/bbr/windowed_filter.h
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
|
||||
// From the Quic BBR implementation in Chromium
|
||||
|
||||
// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum)
|
||||
// estimate of a stream of samples over some fixed time interval. (E.g.,
|
||||
// the minimum RTT over the past five minutes.) The algorithm keeps track of
|
||||
// the best, second best, and third best min (or max) estimates, maintaining an
|
||||
// invariant that the measurement time of the n'th best >= n-1'th best.
|
||||
|
||||
// The algorithm works as follows. On a reset, all three estimates are set to
|
||||
// the same sample. The second best estimate is then recorded in the second
|
||||
// quarter of the window, and a third best estimate is recorded in the second
|
||||
// half of the window, bounding the worst case error when the true min is
|
||||
// monotonically increasing (or true max is monotonically decreasing) over the
|
||||
// window.
|
||||
//
|
||||
// A new best sample replaces all three estimates, since the new best is lower
|
||||
// (or higher) than everything else in the window and it is the most recent.
|
||||
// The window thus effectively gets reset on every new min. The same property
|
||||
// holds true for second best and third best estimates. Specifically, when a
|
||||
// sample arrives that is better than the second best but not better than the
|
||||
// best, it replaces the second and third best estimates but not the best
|
||||
// estimate. Similarly, a sample that is better than the third best estimate
|
||||
// but not the other estimates replaces only the third best estimate.
|
||||
//
|
||||
// Finally, when the best expires, it is replaced by the second best, which in
|
||||
// turn is replaced by the third best. The newest sample replaces the third
|
||||
// best.
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
|
||||
// Compares two values and returns true if the first is less than or equal
|
||||
// to the second.
|
||||
template <class T>
|
||||
struct MinFilter {
|
||||
bool operator()(const T& lhs, const T& rhs) const { return lhs <= rhs; }
|
||||
};
|
||||
|
||||
// Compares two values and returns true if the first is greater than or equal
|
||||
// to the second.
|
||||
template <class T>
|
||||
struct MaxFilter {
|
||||
bool operator()(const T& lhs, const T& rhs) const { return lhs >= rhs; }
|
||||
};
|
||||
|
||||
// Use the following to construct a windowed filter object of type T.
|
||||
// For example, a min filter using Timestamp as the time type:
|
||||
// WindowedFilter<T, MinFilter<T>, Timestamp, TimeDelta>
|
||||
// ObjectName;
|
||||
// A max filter using 64-bit integers as the time type:
|
||||
// WindowedFilter<T, MaxFilter<T>, uint64_t, int64_t> ObjectName;
|
||||
// Specifically, this template takes four arguments:
|
||||
// 1. T -- type of the measurement that is being filtered.
|
||||
// 2. Compare -- MinFilter<T> or MaxFilter<T>, depending on the type of filter
|
||||
// desired.
|
||||
// 3. TimeT -- the type used to represent timestamps.
|
||||
// 4. TimeDeltaT -- the type used to represent continuous time intervals between
|
||||
// two timestamps. Has to be the type of (a - b) if both |a| and |b| are
|
||||
// of type TimeT.
|
||||
template <class T, class Compare, typename TimeT, typename TimeDeltaT>
|
||||
class WindowedFilter {
|
||||
public:
|
||||
// |window_length| is the period after which a best estimate expires.
|
||||
// |zero_value| is used as the uninitialized value for objects of T.
|
||||
// Importantly, |zero_value| should be an invalid value for a true sample.
|
||||
WindowedFilter(TimeDeltaT window_length, T zero_value, TimeT zero_time)
|
||||
: window_length_(window_length),
|
||||
zero_value_(zero_value),
|
||||
estimates_{Sample(zero_value_, zero_time),
|
||||
Sample(zero_value_, zero_time),
|
||||
Sample(zero_value_, zero_time)} {}
|
||||
|
||||
// Changes the window length. Does not update any current samples.
|
||||
void SetWindowLength(TimeDeltaT window_length) {
|
||||
window_length_ = window_length;
|
||||
}
|
||||
|
||||
// Updates best estimates with |sample|, and expires and updates best
|
||||
// estimates as necessary.
|
||||
void Update(T new_sample, TimeT new_time) {
|
||||
// Reset all estimates if they have not yet been initialized, if new sample
|
||||
// is a new best, or if the newest recorded estimate is too old.
|
||||
if (estimates_[0].sample == zero_value_ ||
|
||||
Compare()(new_sample, estimates_[0].sample) ||
|
||||
new_time - estimates_[2].time > window_length_) {
|
||||
Reset(new_sample, new_time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Compare()(new_sample, estimates_[1].sample)) {
|
||||
estimates_[1] = Sample(new_sample, new_time);
|
||||
estimates_[2] = estimates_[1];
|
||||
} else if (Compare()(new_sample, estimates_[2].sample)) {
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
}
|
||||
|
||||
// Expire and update estimates as necessary.
|
||||
if (new_time - estimates_[0].time > window_length_) {
|
||||
// The best estimate hasn't been updated for an entire window, so promote
|
||||
// second and third best estimates.
|
||||
estimates_[0] = estimates_[1];
|
||||
estimates_[1] = estimates_[2];
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
// Need to iterate one more time. Check if the new best estimate is
|
||||
// outside the window as well, since it may also have been recorded a
|
||||
// long time ago. Don't need to iterate once more since we cover that
|
||||
// case at the beginning of the method.
|
||||
if (new_time - estimates_[0].time > window_length_) {
|
||||
estimates_[0] = estimates_[1];
|
||||
estimates_[1] = estimates_[2];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (estimates_[1].sample == estimates_[0].sample &&
|
||||
new_time - estimates_[1].time > window_length_ >> 2) {
|
||||
// A quarter of the window has passed without a better sample, so the
|
||||
// second-best estimate is taken from the second quarter of the window.
|
||||
estimates_[2] = estimates_[1] = Sample(new_sample, new_time);
|
||||
return;
|
||||
}
|
||||
|
||||
if (estimates_[2].sample == estimates_[1].sample &&
|
||||
new_time - estimates_[2].time > window_length_ >> 1) {
|
||||
// We've passed a half of the window without a better estimate, so take
|
||||
// a third-best estimate from the second half of the window.
|
||||
estimates_[2] = Sample(new_sample, new_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Resets all estimates to new sample.
|
||||
void Reset(T new_sample, TimeT new_time) {
|
||||
estimates_[0] = estimates_[1] = estimates_[2] =
|
||||
Sample(new_sample, new_time);
|
||||
}
|
||||
|
||||
T GetBest() const { return estimates_[0].sample; }
|
||||
T GetSecondBest() const { return estimates_[1].sample; }
|
||||
T GetThirdBest() const { return estimates_[2].sample; }
|
||||
|
||||
private:
|
||||
struct Sample {
|
||||
T sample;
|
||||
TimeT time;
|
||||
Sample(T init_sample, TimeT init_time)
|
||||
: sample(init_sample), time(init_time) {}
|
||||
};
|
||||
|
||||
TimeDeltaT window_length_; // Time length of window.
|
||||
T zero_value_; // Uninitialized value of T.
|
||||
Sample estimates_[3]; // Best estimate is element 0.
|
||||
};
|
||||
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_BBR_WINDOWED_FILTER_H_
|
||||
356
modules/congestion_controller/bbr/windowed_filter_unittest.cc
Normal file
356
modules/congestion_controller/bbr/windowed_filter_unittest.cc
Normal file
@ -0,0 +1,356 @@
|
||||
/*
|
||||
* 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/windowed_filter.h"
|
||||
|
||||
#include "modules/congestion_controller/bbr/rtt_stats.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace bbr {
|
||||
namespace test {
|
||||
class WindowedFilterTest : public ::testing::Test {
|
||||
public:
|
||||
// Set the window to 99ms, so 25ms is more than a quarter rtt.
|
||||
WindowedFilterTest()
|
||||
: windowed_min_rtt_(99, TimeDelta::Zero(), 0),
|
||||
windowed_max_bw_(99, DataRate::Zero(), 0) {}
|
||||
|
||||
// Sets up windowed_min_rtt_ to have the following values:
|
||||
// Best = 20ms, recorded at 25ms
|
||||
// Second best = 40ms, recorded at 75ms
|
||||
// Third best = 50ms, recorded at 100ms
|
||||
void InitializeMinFilter() {
|
||||
int64_t now_ms = 0;
|
||||
TimeDelta rtt_sample = TimeDelta::ms(10);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << rtt_sample
|
||||
<< " mins: "
|
||||
<< " " << windowed_min_rtt_.GetBest() << " "
|
||||
<< windowed_min_rtt_.GetSecondBest() << " "
|
||||
<< windowed_min_rtt_.GetThirdBest();
|
||||
now_ms += 25;
|
||||
rtt_sample = rtt_sample + TimeDelta::ms(10);
|
||||
}
|
||||
EXPECT_EQ(TimeDelta::ms(20), windowed_min_rtt_.GetBest());
|
||||
EXPECT_EQ(TimeDelta::ms(40), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::ms(50), windowed_min_rtt_.GetThirdBest());
|
||||
}
|
||||
|
||||
// Sets up windowed_max_bw_ to have the following values:
|
||||
// Best = 900 bps, recorded at 25ms
|
||||
// Second best = 700 bps, recorded at 75ms
|
||||
// Third best = 600 bps, recorded at 100ms
|
||||
void InitializeMaxFilter() {
|
||||
int64_t now_ms = 0;
|
||||
DataRate bw_sample = DataRate::bps(1000);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << bw_sample << " maxs: "
|
||||
<< " " << windowed_max_bw_.GetBest() << " "
|
||||
<< windowed_max_bw_.GetSecondBest() << " "
|
||||
<< windowed_max_bw_.GetThirdBest();
|
||||
now_ms += 25;
|
||||
bw_sample = DataRate::bps(bw_sample.bps() - 100);
|
||||
}
|
||||
EXPECT_EQ(DataRate::bps(900), windowed_max_bw_.GetBest());
|
||||
EXPECT_EQ(DataRate::bps(700), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::bps(600), windowed_max_bw_.GetThirdBest());
|
||||
}
|
||||
|
||||
protected:
|
||||
WindowedFilter<TimeDelta, MinFilter<TimeDelta>, int64_t, int64_t>
|
||||
windowed_min_rtt_;
|
||||
WindowedFilter<DataRate, MaxFilter<DataRate>, int64_t, int64_t>
|
||||
windowed_max_bw_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
// Test helper function: updates the filter with a lot of small values in order
|
||||
// to ensure that it is not susceptible to noise.
|
||||
void UpdateWithIrrelevantSamples(
|
||||
WindowedFilter<uint64_t, MaxFilter<uint64_t>, uint64_t, uint64_t>* filter,
|
||||
uint64_t max_value,
|
||||
uint64_t time) {
|
||||
for (uint64_t i = 0; i < 1000; i++) {
|
||||
filter->Update(i % max_value, time);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(WindowedFilterTest, UninitializedEstimates) {
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetBest());
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::Zero(), windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::Zero(), windowed_max_bw_.GetThirdBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, MonotonicallyIncreasingMin) {
|
||||
int64_t now_ms = 0;
|
||||
TimeDelta rtt_sample = TimeDelta::ms(10);
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(TimeDelta::ms(10), windowed_min_rtt_.GetBest());
|
||||
|
||||
// Gradually increase the rtt samples and ensure the windowed min rtt starts
|
||||
// rising.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
now_ms += 25;
|
||||
rtt_sample = rtt_sample + TimeDelta::ms(10);
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << rtt_sample.ms()
|
||||
<< " mins: "
|
||||
<< " " << windowed_min_rtt_.GetBest().ms() << " "
|
||||
<< windowed_min_rtt_.GetSecondBest().ms() << " "
|
||||
<< windowed_min_rtt_.GetThirdBest().ms();
|
||||
if (i < 3) {
|
||||
EXPECT_EQ(TimeDelta::ms(10), windowed_min_rtt_.GetBest());
|
||||
} else if (i == 3) {
|
||||
EXPECT_EQ(TimeDelta::ms(20), windowed_min_rtt_.GetBest());
|
||||
} else if (i < 6) {
|
||||
EXPECT_EQ(TimeDelta::ms(40), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, MonotonicallyDecreasingMax) {
|
||||
int64_t now_ms = 0;
|
||||
DataRate bw_sample = DataRate::bps(1000);
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(DataRate::bps(1000), windowed_max_bw_.GetBest());
|
||||
|
||||
// Gradually decrease the bw samples and ensure the windowed max bw starts
|
||||
// decreasing.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
now_ms += 25;
|
||||
bw_sample = DataRate::bps(bw_sample.bps() - 100);
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
RTC_LOG(LS_VERBOSE) << "i: " << i << " sample: " << bw_sample.bps()
|
||||
<< " maxs: "
|
||||
<< " " << windowed_max_bw_.GetBest().bps() << " "
|
||||
<< windowed_max_bw_.GetSecondBest().bps() << " "
|
||||
<< windowed_max_bw_.GetThirdBest().bps();
|
||||
if (i < 3) {
|
||||
EXPECT_EQ(DataRate::bps(1000), windowed_max_bw_.GetBest());
|
||||
} else if (i == 3) {
|
||||
EXPECT_EQ(DataRate::bps(900), windowed_max_bw_.GetBest());
|
||||
} else if (i < 6) {
|
||||
EXPECT_EQ(DataRate::bps(700), windowed_max_bw_.GetBest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesThirdBestMin) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the third-choice min-rtt sets that, but nothing else.
|
||||
TimeDelta rtt_sample = windowed_min_rtt_.GetThirdBest() - TimeDelta::ms(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetThirdBest(), TimeDelta::ms(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(TimeDelta::ms(40), windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::ms(20), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesThirdBestMax) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the third-choice max sets that, but nothing else.
|
||||
DataRate bw_sample =
|
||||
DataRate::bps(windowed_max_bw_.GetThirdBest().bps() + 50);
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(DataRate::bps(700), windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::bps(900), windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesSecondBestMin) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the second-choice min sets that and also
|
||||
// the third-choice min.
|
||||
TimeDelta rtt_sample = windowed_min_rtt_.GetSecondBest() - TimeDelta::ms(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetSecondBest(), TimeDelta::ms(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(TimeDelta::ms(20), windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesSecondBestMax) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the second-choice max sets that and also
|
||||
// the third-choice max.
|
||||
DataRate bw_sample =
|
||||
DataRate::bps(windowed_max_bw_.GetSecondBest().bps() + 50);
|
||||
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(DataRate::bps(900), windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesAllMins) {
|
||||
InitializeMinFilter();
|
||||
// RTT sample lower than the first-choice min-rtt sets that and also
|
||||
// the second and third-choice mins.
|
||||
TimeDelta rtt_sample = windowed_min_rtt_.GetBest() - TimeDelta::ms(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_GT(windowed_min_rtt_.GetBest(), TimeDelta::ms(5));
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, SampleChangesAllMaxs) {
|
||||
InitializeMaxFilter();
|
||||
// BW sample higher than the first-choice max sets that and also
|
||||
// the second and third-choice maxs.
|
||||
DataRate bw_sample = DataRate::bps(windowed_max_bw_.GetBest().bps() + 50);
|
||||
// Latest sample was recorded at 100ms.
|
||||
int64_t now_ms = 101;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireBestMin) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta old_third_best = windowed_min_rtt_.GetThirdBest();
|
||||
TimeDelta old_second_best = windowed_min_rtt_.GetSecondBest();
|
||||
TimeDelta rtt_sample = old_third_best + TimeDelta::ms(5);
|
||||
// Best min sample was recorded at 25ms, so expiry time is 124ms.
|
||||
int64_t now_ms = 125;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(old_third_best, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(old_second_best, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireBestMax) {
|
||||
InitializeMaxFilter();
|
||||
DataRate old_third_best = windowed_max_bw_.GetThirdBest();
|
||||
DataRate old_second_best = windowed_max_bw_.GetSecondBest();
|
||||
DataRate bw_sample = DataRate::bps(old_third_best.bps() - 50);
|
||||
// Best max sample was recorded at 25ms, so expiry time is 124ms.
|
||||
int64_t now_ms = 125;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(old_third_best, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(old_second_best, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireSecondBestMin) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta old_third_best = windowed_min_rtt_.GetThirdBest();
|
||||
TimeDelta rtt_sample = old_third_best + TimeDelta::ms(5);
|
||||
// Second best min sample was recorded at 75ms, so expiry time is 174ms.
|
||||
int64_t now_ms = 175;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(old_third_best, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireSecondBestMax) {
|
||||
InitializeMaxFilter();
|
||||
DataRate old_third_best = windowed_max_bw_.GetThirdBest();
|
||||
DataRate bw_sample = DataRate::bps(old_third_best.bps() - 50);
|
||||
// Second best max sample was recorded at 75ms, so expiry time is 174ms.
|
||||
int64_t now_ms = 175;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(old_third_best, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireAllMins) {
|
||||
InitializeMinFilter();
|
||||
TimeDelta rtt_sample = windowed_min_rtt_.GetThirdBest() + TimeDelta::ms(5);
|
||||
// This assert is necessary to avoid triggering -Wstrict-overflow
|
||||
// See crbug/616957
|
||||
ASSERT_LT(windowed_min_rtt_.GetThirdBest(), TimeDelta::Infinity());
|
||||
// Third best min sample was recorded at 100ms, so expiry time is 199ms.
|
||||
int64_t now_ms = 200;
|
||||
windowed_min_rtt_.Update(rtt_sample, now_ms);
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetThirdBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetSecondBest());
|
||||
EXPECT_EQ(rtt_sample, windowed_min_rtt_.GetBest());
|
||||
}
|
||||
|
||||
TEST_F(WindowedFilterTest, ExpireAllMaxs) {
|
||||
InitializeMaxFilter();
|
||||
DataRate bw_sample =
|
||||
DataRate::bps(windowed_max_bw_.GetThirdBest().bps() - 50);
|
||||
// Third best max sample was recorded at 100ms, so expiry time is 199ms.
|
||||
int64_t now_ms = 200;
|
||||
windowed_max_bw_.Update(bw_sample, now_ms);
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetThirdBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetSecondBest());
|
||||
EXPECT_EQ(bw_sample, windowed_max_bw_.GetBest());
|
||||
}
|
||||
|
||||
// Test the windowed filter where the time used is an exact counter instead of a
|
||||
// timestamp. This is useful if, for example, the time is measured in round
|
||||
// trips.
|
||||
TEST_F(WindowedFilterTest, ExpireCounterBasedMax) {
|
||||
// Create a window which starts at t = 0 and expires after two cycles.
|
||||
WindowedFilter<uint64_t, MaxFilter<uint64_t>, uint64_t, uint64_t> max_filter(
|
||||
2, 0, 0);
|
||||
|
||||
const uint64_t kBest = 50000;
|
||||
// Insert 50000 at t = 1.
|
||||
max_filter.Update(50000, 1);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 1);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
|
||||
// Insert 40000 at t = 2. Nothing is expected to expire.
|
||||
max_filter.Update(40000, 2);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 2);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
|
||||
// Insert 30000 at t = 3. Nothing is expected to expire yet.
|
||||
max_filter.Update(30000, 3);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 3);
|
||||
EXPECT_EQ(kBest, max_filter.GetBest());
|
||||
RTC_LOG(LS_VERBOSE) << max_filter.GetSecondBest();
|
||||
RTC_LOG(LS_VERBOSE) << max_filter.GetThirdBest();
|
||||
|
||||
// Insert 20000 at t = 4. 50000 at t = 1 expires, so 40000 becomes the new
|
||||
// maximum.
|
||||
const uint64_t kNewBest = 40000;
|
||||
max_filter.Update(20000, 4);
|
||||
EXPECT_EQ(kNewBest, max_filter.GetBest());
|
||||
UpdateWithIrrelevantSamples(&max_filter, 20, 4);
|
||||
EXPECT_EQ(kNewBest, max_filter.GetBest());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace bbr
|
||||
} // namespace webrtc
|
||||
Reference in New Issue
Block a user