blob: eda644b4ca9bc7cc112693141663398fc81efae2 [file] [log] [blame]
/*
* Copyright (c) 2016 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 "rtc_base/rate_limiter.h"
#include <memory>
#include "rtc_base/event.h"
#include "rtc_base/platform_thread.h"
#include "system_wrappers/include/clock.h"
#include "test/gtest.h"
namespace webrtc {
class RateLimitTest : public ::testing::Test {
public:
RateLimitTest()
: clock_(0), rate_limiter(new RateLimiter(&clock_, kWindowSizeMs)) {}
~RateLimitTest() override {}
void SetUp() override { rate_limiter->SetMaxRate(kMaxRateBps); }
protected:
static constexpr int64_t kWindowSizeMs = 1000;
static constexpr uint32_t kMaxRateBps = 100000;
// Bytes needed to completely saturate the rate limiter.
static constexpr size_t kRateFillingBytes =
(kMaxRateBps * kWindowSizeMs) / (8 * 1000);
SimulatedClock clock_;
std::unique_ptr<RateLimiter> rate_limiter;
};
TEST_F(RateLimitTest, IncreasingMaxRate) {
// Fill rate, extend window to full size.
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
clock_.AdvanceTimeMilliseconds(kWindowSizeMs - 1);
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
// All rate consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
// Double the available rate and fill that too.
rate_limiter->SetMaxRate(kMaxRateBps * 2);
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes));
// All rate consumed again.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
}
TEST_F(RateLimitTest, DecreasingMaxRate) {
// Fill rate, extend window to full size.
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
clock_.AdvanceTimeMilliseconds(kWindowSizeMs - 1);
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
// All rate consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
// Halve the available rate and move window so half of the data falls out.
rate_limiter->SetMaxRate(kMaxRateBps / 2);
clock_.AdvanceTimeMilliseconds(1);
// All rate still consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
}
TEST_F(RateLimitTest, ChangingWindowSize) {
// Fill rate, extend window to full size.
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
clock_.AdvanceTimeMilliseconds(kWindowSizeMs - 1);
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
// All rate consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
// Decrease window size so half of the data falls out.
rate_limiter->SetWindowSize(kWindowSizeMs / 2);
// Average rate should still be the same, so rate is still all consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
// Increase window size again. Now the rate is only half used (removed data
// points don't come back to life).
rate_limiter->SetWindowSize(kWindowSizeMs);
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes / 2));
// All rate consumed again.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
}
TEST_F(RateLimitTest, SingleUsageAlwaysOk) {
// Using more bytes than can fit in a window is OK for a single packet.
EXPECT_TRUE(rate_limiter->TryUseRate(kRateFillingBytes + 1));
}
TEST_F(RateLimitTest, WindowSizeLimits) {
EXPECT_TRUE(rate_limiter->SetWindowSize(1));
EXPECT_FALSE(rate_limiter->SetWindowSize(0));
EXPECT_TRUE(rate_limiter->SetWindowSize(kWindowSizeMs));
EXPECT_FALSE(rate_limiter->SetWindowSize(kWindowSizeMs + 1));
}
static const int64_t kMaxTimeoutMs = 30000;
class ThreadTask {
public:
explicit ThreadTask(RateLimiter* rate_limiter)
: rate_limiter_(rate_limiter) {}
virtual ~ThreadTask() {}
void Run() {
start_signal_.Wait(kMaxTimeoutMs);
DoRun();
end_signal_.Set();
}
virtual void DoRun() = 0;
RateLimiter* const rate_limiter_;
rtc::Event start_signal_;
rtc::Event end_signal_;
};
TEST_F(RateLimitTest, MultiThreadedUsage) {
// Simple sanity test, with different threads calling the various methods.
// Runs a few simple tasks, each on its own thread, but coordinated with
// events so that they run in a serialized order. Intended to catch data
// races when run with tsan et al.
// Half window size, double rate -> same amount of bytes needed to fill rate.
class SetWindowSizeTask : public ThreadTask {
public:
explicit SetWindowSizeTask(RateLimiter* rate_limiter)
: ThreadTask(rate_limiter) {}
~SetWindowSizeTask() override {}
void DoRun() override {
EXPECT_TRUE(rate_limiter_->SetWindowSize(kWindowSizeMs / 2));
}
} set_window_size_task(rate_limiter.get());
auto thread1 = rtc::PlatformThread::SpawnJoinable(
[&set_window_size_task] { set_window_size_task.Run(); }, "Thread1");
class SetMaxRateTask : public ThreadTask {
public:
explicit SetMaxRateTask(RateLimiter* rate_limiter)
: ThreadTask(rate_limiter) {}
~SetMaxRateTask() override {}
void DoRun() override { rate_limiter_->SetMaxRate(kMaxRateBps * 2); }
} set_max_rate_task(rate_limiter.get());
auto thread2 = rtc::PlatformThread::SpawnJoinable(
[&set_max_rate_task] { set_max_rate_task.Run(); }, "Thread2");
class UseRateTask : public ThreadTask {
public:
UseRateTask(RateLimiter* rate_limiter, SimulatedClock* clock)
: ThreadTask(rate_limiter), clock_(clock) {}
~UseRateTask() override {}
void DoRun() override {
EXPECT_TRUE(rate_limiter_->TryUseRate(kRateFillingBytes / 2));
clock_->AdvanceTimeMilliseconds((kWindowSizeMs / 2) - 1);
EXPECT_TRUE(rate_limiter_->TryUseRate(kRateFillingBytes / 2));
}
SimulatedClock* const clock_;
} use_rate_task(rate_limiter.get(), &clock_);
auto thread3 = rtc::PlatformThread::SpawnJoinable(
[&use_rate_task] { use_rate_task.Run(); }, "Thread3");
set_window_size_task.start_signal_.Set();
EXPECT_TRUE(set_window_size_task.end_signal_.Wait(kMaxTimeoutMs));
set_max_rate_task.start_signal_.Set();
EXPECT_TRUE(set_max_rate_task.end_signal_.Wait(kMaxTimeoutMs));
use_rate_task.start_signal_.Set();
EXPECT_TRUE(use_rate_task.end_signal_.Wait(kMaxTimeoutMs));
// All rate consumed.
EXPECT_FALSE(rate_limiter->TryUseRate(1));
}
} // namespace webrtc