#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace NBucketQuoterTest { // thread unsafe struct TMockTimer { using TTime = i64; static constexpr ui64 Resolution = 1'000'000ull; // microseconds static TTime CurrentTime; static TMockTimer::TTime Now() { return CurrentTime; } static ui64 Duration(TMockTimer::TTime from, TMockTimer::TTime to) { return to - from; } static void Sleep(TDuration duration) { CurrentTime += duration.MicroSeconds(); } }; template void Sleep(TDuration duration) { ::Sleep(duration); } template <> void Sleep(TDuration duration); template using QuoterTemplate = TBucketQuoter; template struct TTestScenario { void operator () (TBucketQuoter& quoter, ui64 work_time, i64 wait_time, ui64& store_result) const { typename Timer::TTime start = Timer::Now(); work_time *= Timer::Resolution; while (Timer::Duration(start, Timer::Now()) < work_time) { while(!quoter.IsAvail()) { NBucketQuoterTest::Sleep(TDuration::MicroSeconds(quoter.GetWaitTime())); } quoter.Use(1); ++store_result; if (wait_time != 0) { NBucketQuoterTest::Sleep(TDuration::MicroSeconds(wait_time)); } } } }; template class Scenario> ui32 Run(ui64 thread_count, ui64 rps, ui64 seconds, i64 wait_time = 0) { TBucketQuoter quoter(rps, rps); std::vector threads; std::vector results; threads.reserve(thread_count); results.reserve(thread_count); for (ui32 i = 0; i < thread_count; ++i) { results.emplace_back(0); threads.emplace_back(Scenario{}, std::ref(quoter), seconds, wait_time, std::ref(results.back())); } for (ui32 i = 0; i < thread_count; ++i) { threads[i].join(); } return std::reduce(results.begin(), results.end(), 0, std::plus<>()); } }