diff --git a/source/common/rate_limiter_impl.cc b/source/common/rate_limiter_impl.cc index 8c8e13d13..89e548353 100644 --- a/source/common/rate_limiter_impl.cc +++ b/source/common/rate_limiter_impl.cc @@ -96,7 +96,6 @@ bool LinearRampingRateLimiterImpl::tryAcquireOne() { acquired_count_++; return acquireable_count_--; } - const std::chrono::nanoseconds elapsed_time = elapsed(); double elapsed_fraction = 1.0; if (elapsed_time < ramp_time_) { @@ -191,4 +190,21 @@ GraduallyOpeningRateLimiterFilter::GraduallyOpeningRateLimiterFilter( } } +ZipfRateLimiterImpl::ZipfRateLimiterImpl(RateLimiterPtr&& rate_limiter, double q, double v, + ZipfBehavior behavior) + : FilteringRateLimiterImpl(std::move(rate_limiter), + [this]() { + return behavior_ == ZipfBehavior::ZIPF_PSEUDO_RANDOM ? dist_(mt_) + : dist_(g_); + }), + behavior_(behavior) { + if (v <= 0) { + throw NighthawkException("v should be > 0"); + } + if (q <= 1) { + throw NighthawkException("q should be > 1"); + } + dist_ = absl::zipf_distribution(1, q, v); +} + } // namespace Nighthawk \ No newline at end of file diff --git a/source/common/rate_limiter_impl.h b/source/common/rate_limiter_impl.h index 71833237a..b32392b2a 100644 --- a/source/common/rate_limiter_impl.h +++ b/source/common/rate_limiter_impl.h @@ -11,6 +11,7 @@ #include "common/frequency.h" #include "absl/random/random.h" +#include "absl/random/zipf_distribution.h" #include "absl/types/optional.h" namespace Nighthawk { @@ -203,4 +204,30 @@ class GraduallyOpeningRateLimiterFilter : public FilteringRateLimiterImpl { const std::chrono::nanoseconds ramp_time_; }; +/** + * Thin wrapper around absl::zipf_distribution that will pull zeroes and ones from the distribution + * with the intent to probabilistically suppress the wrapped rate limiter. + * This may need further consideration, because it will shoot holes in the pacing, lowering the + * actual achieved frequency. + */ +class ZipfRateLimiterImpl : public FilteringRateLimiterImpl { +public: + enum class ZipfBehavior { ZIPF_PSEUDO_RANDOM, ZIPF_RANDOM }; + /** + * From the absl header associated to the zipf distribution: + * The parameters v and q determine the skew of the distribution. + * zipf_distribution produces random integer-values in the range [0, k], + * distributed according to the discrete probability function: P(x) = (v + x) ^ -q. + * Preconditions: v > 0, q > 1, configuring otherwise throws a NighthawkException. + */ + ZipfRateLimiterImpl(RateLimiterPtr&& rate_limiter, double q = 2.0, double v = 1.0, + ZipfBehavior behavior = ZipfBehavior::ZIPF_RANDOM); + +private: + absl::zipf_distribution dist_; + absl::InsecureBitGen g_; + std::mt19937_64 mt_; + ZipfBehavior behavior_; +}; + } // namespace Nighthawk \ No newline at end of file diff --git a/test/rate_limiter_test.cc b/test/rate_limiter_test.cc index 5e683a719..429425d10 100644 --- a/test/rate_limiter_test.cc +++ b/test/rate_limiter_test.cc @@ -339,4 +339,49 @@ TEST_F(GraduallyOpeningRateLimiterFilterTest, TimingVerificationTest) { 780, 800, 820, 840, 860, 880, 900, 920, 940, 960, 980, 1000})); } +class ZipfRateLimiterImplTest : public Test {}; + +TEST_F(ZipfRateLimiterImplTest, TimingVerificationTest) { + Envoy::Event::SimulatedTimeSystem time_system; + const double q = 2.0; + const double v = 1.0; + auto rate_limiter = std::make_unique( + std::make_unique(time_system, 10_Hz), q, v, + ZipfRateLimiterImpl::ZipfBehavior::ZIPF_PSEUDO_RANDOM); + const std::chrono::seconds duration = 15s; + std::vector aquisition_timings; + auto total_ms_elapsed = 0ms; + auto clock_tick = 1ms; + + do { + if (rate_limiter->tryAcquireOne()) { + aquisition_timings.push_back(total_ms_elapsed.count()); + } + time_system.sleep(clock_tick); + total_ms_elapsed += clock_tick; + } while (total_ms_elapsed <= duration); + EXPECT_EQ(aquisition_timings, + std::vector({500, 800, 1300, 2400, 2900, 3900, 4200, 4400, 4500, + 5800, 6000, 6400, 7900, 8400, 8600, 9900, 10200, 10500, + 10600, 12000, 12300, 12600, 13300, 13600, 13700, 13800, 13900})); +} + +TEST_F(ZipfRateLimiterImplTest, BadArgumentsTest) { + // Zipf preconditions are q > 1, v > 0, verify we guard appropriately. + std::list> bad_q_v_pairs{ + {1.0, 1.0} /*borderline bad q*/, + {1.1, 0.0} /*borderline bad v*/, + {1.0, 0.0} /*borderline bad both*/, + {0.9, 1.0}, + {1.1, -1.0}, + {-1, 1.0}, + }; + + for (const auto& pair : bad_q_v_pairs) { + EXPECT_THROW(ZipfRateLimiterImpl rate_limiter(std::make_unique>(), + std::get<0>(pair), std::get<1>(pair)), + NighthawkException); + } +} + } // namespace Nighthawk