diff options
| author | Stefan Boberg <[email protected]> | 2021-09-27 00:17:45 +0200 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2021-09-27 00:17:45 +0200 |
| commit | bca8f116ed7a3ab484df62c15e9f49e0167e2b2f (patch) | |
| tree | 3e01bd4ef2fc060486681725e4106286961cc132 /zencore/stats.cpp | |
| parent | Fixed up some internals for coding conventions (diff) | |
| download | zen-bca8f116ed7a3ab484df62c15e9f49e0167e2b2f.tar.xz zen-bca8f116ed7a3ab484df62c15e9f49e0167e2b2f.zip | |
stats: Completed Meter implementation
Diffstat (limited to 'zencore/stats.cpp')
| -rw-r--r-- | zencore/stats.cpp | 241 |
1 files changed, 217 insertions, 24 deletions
diff --git a/zencore/stats.cpp b/zencore/stats.cpp index c5187940e..9ae2ddd28 100644 --- a/zencore/stats.cpp +++ b/zencore/stats.cpp @@ -2,6 +2,7 @@ #include "zencore/stats.h" #include <cmath> +#include "zencore/thread.h" #include "zencore/timer.h" #if ZEN_WITH_TESTS @@ -14,62 +15,254 @@ namespace zen { -static constexpr int kTickInterval = 5; // In seconds -static constexpr double kSecondsPerMinute = 60.0; -static constexpr int kOneMinute = 1; -static constexpr int kFiveMinutes = 5; -static constexpr int kFifteenMinutes = 15; +static constinit int kTickIntervalInSeconds = 5; +static constinit double kSecondsPerMinute = 60.0; +static constinit int kOneMinute = 1; +static constinit int kFiveMinutes = 5; +static constinit int kFifteenMinutes = 15; -static double kM1_ALPHA = 1.0 - std::exp(-kTickInterval / kSecondsPerMinute / kOneMinute); -static double kM5_ALPHA = 1.0 - std::exp(-kTickInterval / kSecondsPerMinute / kFiveMinutes); -static double kM15_ALPHA = 1.0 - std::exp(-kTickInterval / kSecondsPerMinute / kFifteenMinutes); +static const double kM1_ALPHA = 1.0 - std::exp(-kTickIntervalInSeconds / kSecondsPerMinute / kOneMinute); +static const double kM5_ALPHA = 1.0 - std::exp(-kTickIntervalInSeconds / kSecondsPerMinute / kFiveMinutes); +static const double kM15_ALPHA = 1.0 - std::exp(-kTickIntervalInSeconds / kSecondsPerMinute / kFifteenMinutes); -static uint64_t CountPerTick = GetHifreqTimerFrequencySafe() * kTickInterval; -static uint64_t CountPerSecond = GetHifreqTimerFrequencySafe(); +static const uint64_t CountPerTick = GetHifreqTimerFrequencySafe() * kTickIntervalInSeconds; +static const uint64_t CountPerSecond = GetHifreqTimerFrequencySafe(); + +////////////////////////////////////////////////////////////////////////// void -EWMA::Tick(double Alpha, uint64_t Interval, uint64_t Count, bool IsInitialUpdate) +RawEWMA::Tick(double Alpha, uint64_t Interval, uint64_t Count, bool IsInitialUpdate) { - double InstantRate = double(Count) / Interval; + const double InstantRate = double(Count) / Interval; if (IsInitialUpdate) { - m_rate = InstantRate; + m_Rate.store(InstantRate, std::memory_order_release); } else { - m_rate += Alpha * (InstantRate - m_rate); + m_Rate.fetch_add(Alpha * (InstantRate - m_Rate)); } } double -EWMA::Rate() const +RawEWMA::Rate() const { - return m_rate * CountPerSecond; + return m_Rate.load(std::memory_order_relaxed) * CountPerSecond; +} + +////////////////////////////////////////////////////////////////////////// + +Meter::Meter() : m_StartTick{GetHifreqTimerValue()}, m_LastTick(m_StartTick.load()) +{ +} + +Meter::~Meter() +{ +} + +void +Meter::TickIfNecessary() +{ + uint64_t OldTick = m_LastTick.load(); + const uint64_t NewTick = GetHifreqTimerValue(); + const uint64_t Age = NewTick - OldTick; + + if (Age > CountPerTick) + { + // Ensure only one thread at a time updates the time. This + // works because our tick interval should be sufficiently + // long to ensure two threads don't end up inside this block + + if (m_LastTick.compare_exchange_strong(OldTick, NewTick)) + { + m_Remain.fetch_add(Age); + + do + { + int64_t Remain = m_Remain.load(std::memory_order_relaxed); + + if (Remain < 0) + { + return; + } + + if (m_Remain.compare_exchange_strong(Remain, Remain - CountPerTick)) + { + Tick(); + } + } while (true); + } + } +} + +void +Meter::Tick() +{ + const uint64_t PendingCount = m_PendingCount.exchange(0); + const bool IsFirstTick = m_IsFirstTick; + + if (IsFirstTick) + { + m_IsFirstTick = false; + } + + m_RateM1.Tick(kM1_ALPHA, CountPerTick, PendingCount, IsFirstTick); + m_RateM5.Tick(kM5_ALPHA, CountPerTick, PendingCount, IsFirstTick); + m_RateM15.Tick(kM15_ALPHA, CountPerTick, PendingCount, IsFirstTick); +} + +double +Meter::Rate1() +{ + TickIfNecessary(); + + return m_RateM1.Rate(); +} + +double +Meter::Rate5() +{ + TickIfNecessary(); + + return m_RateM5.Rate(); +} + +double +Meter::Rate15() +{ + TickIfNecessary(); + + return m_RateM15.Rate(); +} + +double +Meter::MeanRate() +{ + const uint64_t Count = m_TotalCount.load(std::memory_order_relaxed); + + if (Count == 0) + { + return 0.0; + } + + const uint64_t Elapsed = GetHifreqTimerValue() - m_StartTick; + + return (double(Count) * GetHifreqTimerFrequency()) / Elapsed; +} + +void +Meter::Mark(uint64_t Count) +{ + TickIfNecessary(); + + m_TotalCount.fetch_add(Count); + m_PendingCount.fetch_add(Count); } ////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS -TEST_CASE("Stats") +TEST_CASE("EWMA") { - SUBCASE("Simple") + SUBCASE("Simple_1") + { + RawEWMA Ewma1; + Ewma1.Tick(kM1_ALPHA, CountPerSecond, 5, true); + + CHECK(fabs(Ewma1.Rate() - 5) < 0.1); + + for (int i = 0; i < 60; ++i) + { + Ewma1.Tick(kM1_ALPHA, CountPerSecond, 10, false); + } + + CHECK(fabs(Ewma1.Rate() - 10) < 0.1); + + for (int i = 0; i < 60; ++i) + { + Ewma1.Tick(kM1_ALPHA, CountPerSecond, 20, false); + } + + CHECK(fabs(Ewma1.Rate() - 20) < 0.1); + } + + SUBCASE("Simple_10") { - EWMA ewma1; - ewma1.Tick(kM1_ALPHA, CountPerSecond, 5, true); + RawEWMA Ewma1; + RawEWMA Ewma5; + RawEWMA Ewma15; + Ewma1.Tick(kM1_ALPHA, CountPerSecond, 5, true); + Ewma5.Tick(kM5_ALPHA, CountPerSecond, 5, true); + Ewma15.Tick(kM15_ALPHA, CountPerSecond, 5, true); + + CHECK(fabs(Ewma1.Rate() - 5) < 0.1); + CHECK(fabs(Ewma5.Rate() - 5) < 0.1); + CHECK(fabs(Ewma15.Rate() - 5) < 0.1); - CHECK(ewma1.Rate() - 5 < 0.001); + auto Tick1 = [&Ewma1](auto Value) { Ewma1.Tick(kM1_ALPHA, CountPerSecond, Value, false); }; + auto Tick5 = [&Ewma5](auto Value) { Ewma5.Tick(kM5_ALPHA, CountPerSecond, Value, false); }; + auto Tick15 = [&Ewma15](auto Value) { Ewma15.Tick(kM15_ALPHA, CountPerSecond, Value, false); }; for (int i = 0; i < 60; ++i) - ewma1.Tick(kM1_ALPHA, CountPerSecond, 10, false); + { + Tick1(10); + Tick5(10); + Tick15(10); + } + + CHECK(fabs(Ewma1.Rate() - 10) < 0.1); + + for (int i = 0; i < 5 * 60; ++i) + { + Tick1(20); + Tick5(20); + Tick15(20); + } - CHECK(ewma1.Rate() - 10 < 0.001); + CHECK(fabs(Ewma1.Rate() - 20) < 0.1); + CHECK(fabs(Ewma5.Rate() - 20) < 0.1); - ewma1.Tick(kM1_ALPHA, CountPerSecond, 10, false); + for (int i = 0; i < 16 * 60; ++i) + { + Tick1(100); + Tick5(100); + Tick15(100); + } + + CHECK(fabs(Ewma1.Rate() - 100) < 0.1); + CHECK(fabs(Ewma5.Rate() - 100) < 0.1); + CHECK(fabs(Ewma15.Rate() - 100) < 0.5); } } +# if 0 // This is not really a unit test, but mildly useful to exercise some code +TEST_CASE("Meter") +{ + Meter Meter1; + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + Meter1.Mark(1); + Sleep(1000); + [[maybe_unused]] double Rate = Meter1.MeanRate(); +} +# endif + void stats_forcelink() { |