blob: 7ab4588b655608551631e0e95277320935f1faa2 [file] [log] [blame]
/*
* 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 <stdint.h>
#include <string>
#include <type_traits>
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "rtc_base/logging.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: " << ToString(rtt_sample)
<< " mins: "
" "
<< ToString(windowed_min_rtt_.GetBest()) << " "
<< ToString(windowed_min_rtt_.GetSecondBest()) << " "
<< ToString(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: " << ToString(bw_sample)
<< " maxs: "
" "
<< ToString(windowed_max_bw_.GetBest()) << " "
<< ToString(windowed_max_bw_.GetSecondBest()) << " "
<< ToString(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::PlusInfinity());
// 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