| /* | 
 |  *  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 constexpr TimeDelta kMaxTimeout = TimeDelta::Seconds(30); | 
 |  | 
 | class ThreadTask { | 
 |  public: | 
 |   explicit ThreadTask(RateLimiter* rate_limiter) | 
 |       : rate_limiter_(rate_limiter) {} | 
 |   virtual ~ThreadTask() {} | 
 |  | 
 |   void Run() { | 
 |     start_signal_.Wait(kMaxTimeout); | 
 |     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(kMaxTimeout)); | 
 |  | 
 |   set_max_rate_task.start_signal_.Set(); | 
 |   EXPECT_TRUE(set_max_rate_task.end_signal_.Wait(kMaxTimeout)); | 
 |  | 
 |   use_rate_task.start_signal_.Set(); | 
 |   EXPECT_TRUE(use_rate_task.end_signal_.Wait(kMaxTimeout)); | 
 |  | 
 |   // All rate consumed. | 
 |   EXPECT_FALSE(rate_limiter->TryUseRate(1)); | 
 | } | 
 |  | 
 | }  // namespace webrtc |