/* * 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 #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 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