From b0ba558c966c83f7e664d9d0b7effddbb5dfd4c1 Mon Sep 17 00:00:00 2001 From: Sebastian Jansson Date: Thu, 22 Mar 2018 16:38:47 +0100 Subject: [PATCH] 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 Reviewed-by: Philip Eliasson Cr-Commit-Position: refs/heads/master@{#22592} --- modules/congestion_controller/bbr/BUILD.gn | 7 + .../bbr/windowed_filter.h | 168 +++++++++ .../bbr/windowed_filter_unittest.cc | 356 ++++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 modules/congestion_controller/bbr/windowed_filter.h create mode 100644 modules/congestion_controller/bbr/windowed_filter_unittest.cc diff --git a/modules/congestion_controller/bbr/BUILD.gn b/modules/congestion_controller/bbr/BUILD.gn index 6b8bd15073..0e8bb58e1e 100644 --- a/modules/congestion_controller/bbr/BUILD.gn +++ b/modules/congestion_controller/bbr/BUILD.gn @@ -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", ] diff --git a/modules/congestion_controller/bbr/windowed_filter.h b/modules/congestion_controller/bbr/windowed_filter.h new file mode 100644 index 0000000000..14185a5306 --- /dev/null +++ b/modules/congestion_controller/bbr/windowed_filter.h @@ -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 +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 +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, Timestamp, TimeDelta> +// ObjectName; +// A max filter using 64-bit integers as the time type: +// WindowedFilter, uint64_t, int64_t> ObjectName; +// Specifically, this template takes four arguments: +// 1. T -- type of the measurement that is being filtered. +// 2. Compare -- MinFilter or MaxFilter, 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 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_ diff --git a/modules/congestion_controller/bbr/windowed_filter_unittest.cc b/modules/congestion_controller/bbr/windowed_filter_unittest.cc new file mode 100644 index 0000000000..f82421f363 --- /dev/null +++ b/modules/congestion_controller/bbr/windowed_filter_unittest.cc @@ -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, int64_t, int64_t> + windowed_min_rtt_; + WindowedFilter, 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, 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, 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