diff options
Diffstat (limited to 'zencore')
| -rw-r--r-- | zencore/compactbinary.cpp | 29 | ||||
| -rw-r--r-- | zencore/filesystem.cpp | 6 | ||||
| -rw-r--r-- | zencore/include/zencore/iobuffer.h | 9 | ||||
| -rw-r--r-- | zencore/include/zencore/refcount.h | 2 | ||||
| -rw-r--r-- | zencore/include/zencore/stats.h | 146 | ||||
| -rw-r--r-- | zencore/include/zencore/thread.h | 20 | ||||
| -rw-r--r-- | zencore/include/zencore/timer.h | 20 | ||||
| -rw-r--r-- | zencore/iobuffer.cpp | 37 | ||||
| -rw-r--r-- | zencore/memory.cpp | 15 | ||||
| -rw-r--r-- | zencore/stats.cpp | 367 | ||||
| -rw-r--r-- | zencore/timer.cpp | 21 |
11 files changed, 594 insertions, 78 deletions
diff --git a/zencore/compactbinary.cpp b/zencore/compactbinary.cpp index f71c0aaea..f3fbf312c 100644 --- a/zencore/compactbinary.cpp +++ b/zencore/compactbinary.cpp @@ -1474,10 +1474,10 @@ public: Builder << Accessor.AsIntegerNegative(); break; case CbFieldType::Float32: - Builder.Append("%.9g"_format(Accessor.AsFloat32())); + Builder.Append("{:.9g}"_format(Accessor.AsFloat32())); break; case CbFieldType::Float64: - Builder.Append("%.17g"_format(Accessor.AsFloat64())); + Builder.Append("{:.17g}"_format(Accessor.AsFloat64())); break; case CbFieldType::BoolFalse: Builder << "false"sv; @@ -1831,6 +1831,31 @@ TEST_CASE("uson.json") CHECK(ValueOne == "ValueOne"); CHECK(ValueTwo == "ValueTwo"); } + + SUBCASE("number") + { + const double ExpectedFloatValue = 21.21f; + const double ExpectedDoubleValue = 42.42; + + CbObjectWriter Writer; + Writer << "Float" << ExpectedFloatValue; + Writer << "Double" << ExpectedDoubleValue; + + CbObject Obj = Writer.Save(); + + StringBuilder<128> Sb; + const std::string_view JsonText = Obj.ToJson(Sb).ToView(); + + std::string JsonError; + json11::Json Json = json11::Json::parse(JsonText.data(), JsonError); + + const float FloatValue = float(Json["Float"].number_value()); + const double DoubleValue = Json["Double"].number_value(); + + CHECK(JsonError.empty()); + CHECK(FloatValue == doctest::Approx(ExpectedFloatValue)); + CHECK(DoubleValue == doctest::Approx(ExpectedDoubleValue)); + } } #endif diff --git a/zencore/filesystem.cpp b/zencore/filesystem.cpp index 7a1f6c336..2d2603434 100644 --- a/zencore/filesystem.cpp +++ b/zencore/filesystem.cpp @@ -138,9 +138,9 @@ WipeDirectory(const wchar_t* DirPath) } } } while (FindNextFileW(hFind, &FindData) == TRUE); - } - FindClose(hFind); + FindClose(hFind); + } return true; } @@ -819,7 +819,7 @@ TEST_CASE("filesystem") using namespace std::filesystem; // GetExePath - path BinPath = GetRunningExecutablePath(); + path BinPath = GetRunningExecutablePath(); const bool ExpectedExe = BinPath.stem() == "zencore-test" || BinPath.stem() == "zenserver-test"; CHECK(ExpectedExe); CHECK(is_regular_file(BinPath)); diff --git a/zencore/include/zencore/iobuffer.h b/zencore/include/zencore/iobuffer.h index 54801f9ac..36ecbd9a7 100644 --- a/zencore/include/zencore/iobuffer.h +++ b/zencore/include/zencore/iobuffer.h @@ -290,10 +290,6 @@ IoBufferCore::ExtendedCore() const class IoBuffer { public: - enum EAssumeOwnershipTag - { - AssumeOwnership - }; enum ECloneTag { Clone @@ -339,11 +335,6 @@ public: memcpy(const_cast<void*>(m_Core->DataPointer()), DataPtr, SizeBytes); } - inline IoBuffer(EAssumeOwnershipTag, const void* DataPtr, size_t Sz) : m_Core(new IoBufferCore(DataPtr, Sz)) - { - m_Core->SetIsOwnedByThis(true); - } - ZENCORE_API IoBuffer(EFileTag, void* FileHandle, uint64_t ChunkFileOffset, uint64_t ChunkSize); ZENCORE_API IoBuffer(EBorrowedFileTag, void* FileHandle, uint64_t ChunkFileOffset, uint64_t ChunkSize); diff --git a/zencore/include/zencore/refcount.h b/zencore/include/zencore/refcount.h index 0a1e15614..320718f5b 100644 --- a/zencore/include/zencore/refcount.h +++ b/zencore/include/zencore/refcount.h @@ -17,7 +17,7 @@ namespace zen { class RefCounted { public: - RefCounted() = default; + RefCounted() = default; virtual ~RefCounted() = default; inline uint32_t AddRef() const { return AtomicIncrement(const_cast<RefCounted*>(this)->m_RefCount); } diff --git a/zencore/include/zencore/stats.h b/zencore/include/zencore/stats.h index 0554f620d..dfa8dac34 100644 --- a/zencore/include/zencore/stats.h +++ b/zencore/include/zencore/stats.h @@ -2,11 +2,16 @@ #pragma once -#include <atomic> -#include <type_traits> #include "zencore.h" +#include <atomic> +#include <random> + namespace zen { +class CbObjectWriter; +} + +namespace zen::metrics { template<typename T> class Gauge @@ -76,18 +81,19 @@ public: Meter(); ~Meter(); - double Rate1(); // One-minute rate - double Rate5(); // Five-minute rate - double Rate15(); // Fifteen-minute rate - double MeanRate(); // Mean rate since instantiation of this meter - void Mark(uint64_t Count = 1); // Register one or more events + inline uint64_t Count() const { return m_TotalCount; } + double Rate1(); // One-minute rate + double Rate5(); // Five-minute rate + double Rate15(); // Fifteen-minute rate + double MeanRate() const; // Mean rate since instantiation of this meter + void Mark(uint64_t Count = 1); // Register one or more events private: std::atomic<uint64_t> m_TotalCount{0}; // Accumulator counting number of marks since beginning std::atomic<uint64_t> m_PendingCount{0}; // Pending EWMA update accumulator std::atomic<uint64_t> m_StartTick{0}; // Time this was instantiated (for mean) std::atomic<uint64_t> m_LastTick{0}; // Timestamp of last EWMA tick - std::atomic<int64_t> m_Remain{0}; // Tracks the "modulo" of tick time + std::atomic<int64_t> m_Remainder{0}; // Tracks the "modulo" of tick time bool m_IsFirstTick = true; RawEWMA m_RateM1; RawEWMA m_RateM5; @@ -97,6 +103,130 @@ private: void Tick(); }; +/** Moment-in-time snapshot of a distribution + */ +class SampleSnapshot +{ +public: + SampleSnapshot(std::vector<double>&& Values); + ~SampleSnapshot(); + + uint32_t Size() const { return (uint32_t)m_Values.size(); } + double GetQuantileValue(double Quantile); + double GetMedian() { return GetQuantileValue(0.5); } + double Get75Percentile() { return GetQuantileValue(0.75); } + double Get95Percentile() { return GetQuantileValue(0.95); } + double Get98Percentile() { return GetQuantileValue(0.98); } + double Get99Percentile() { return GetQuantileValue(0.99); } + double Get999Percentile() { return GetQuantileValue(0.999); } + const std::vector<double>& GetValues() const; + +private: + std::vector<double> m_Values; +}; + +/** Randomly selects samples from a stream. Uses Vitter's + Algorithm R to produce a statistically representative sample. + + http://www.cs.umd.edu/~samir/498/vitter.pdf - Random Sampling with a Reservoir + */ + +class UniformSample +{ +public: + UniformSample(uint32_t ReservoirSize); + ~UniformSample(); + + void Clear(); + uint32_t Size() const; + void Update(int64_t Value); + SampleSnapshot Snapshot() const; + + template<std::invocable<int64_t> T> + void IterateValues(T Callback) const + { + for (const auto& Value : m_Values) + { + Callback(Value); + } + } + +private: + std::atomic<uint64_t> m_SampleCounter{0}; + std::vector<std::atomic<int64_t>> m_Values; +}; + +/** Track (probabilistic) sample distribution along with min/max + */ +class Histogram +{ +public: + Histogram(int32_t SampleCount = 1028); + ~Histogram(); + + void Clear(); + void Update(int64_t Value); + int64_t Max() const; + int64_t Min() const; + double Mean() const; + uint64_t Count() const; + SampleSnapshot Snapshot() const { return m_Sample.Snapshot(); } + +private: + UniformSample m_Sample; + std::atomic<int64_t> m_Min{0}; + std::atomic<int64_t> m_Max{0}; + std::atomic<int64_t> m_Sum{0}; + std::atomic<int64_t> m_Count{0}; +}; + +/** Track timing and frequency of some operation + + Example usage would be to track frequency and duration of network + requests, or function calls. + + */ +class OperationTiming +{ +public: + OperationTiming(int32_t SampleCount = 514); + ~OperationTiming(); + + void Update(int64_t Duration); + int64_t Max() const; + int64_t Min() const; + double Mean() const; + uint64_t Count() const; + SampleSnapshot Snapshot() const { return m_Histogram.Snapshot(); } + + double Rate1() { return m_Meter.Rate1(); } + double Rate5() { return m_Meter.Rate5(); } + double Rate15() { return m_Meter.Rate15(); } + double MeanRate() const { return m_Meter.MeanRate(); } + + struct Scope + { + Scope(OperationTiming& Outer); + ~Scope(); + + private: + OperationTiming& m_Outer; + uint64_t m_StartTick; + }; + +private: + Meter m_Meter; + Histogram m_Histogram; +}; + +void EmitSnapshot(std::string_view Tag, OperationTiming& Stat, CbObjectWriter& Cbo); +void EmitSnapshot(std::string_view Tag, const Histogram& Stat, CbObjectWriter& Cbo); +void EmitSnapshot(std::string_view Tag, Meter& Stat, CbObjectWriter& Cbo); + +} // namespace zen::metrics + +namespace zen { + extern void stats_forcelink(); } // namespace zen diff --git a/zencore/include/zencore/thread.h b/zencore/include/zencore/thread.h index 7889682cd..410ffbd1e 100644 --- a/zencore/include/zencore/thread.h +++ b/zencore/include/zencore/thread.h @@ -75,12 +75,12 @@ public: ZENCORE_API Event(); ZENCORE_API ~Event(); - Event(Event&& Rhs) : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; } + Event(Event&& Rhs) noexcept : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; } Event(const Event& Rhs) = delete; Event& operator=(const Event& Rhs) = delete; - inline Event& operator=(Event&& Rhs) + inline Event& operator=(Event&& Rhs) noexcept { std::swap(m_EventHandle, Rhs.m_EventHandle); return *this; @@ -133,14 +133,14 @@ public: ZENCORE_API ~ProcessHandle(); - ZENCORE_API void Initialize(int Pid); - ZENCORE_API void Initialize(void* ProcessHandle); /// Initialize with an existing handle - takes ownership of the handle - ZENCORE_API bool IsRunning() const; - ZENCORE_API bool IsValid() const; - ZENCORE_API bool Wait(int TimeoutMs = -1); - ZENCORE_API void Terminate(int ExitCode); - ZENCORE_API void Reset(); - inline int Pid() const { return m_Pid; } + ZENCORE_API void Initialize(int Pid); + ZENCORE_API void Initialize(void* ProcessHandle); /// Initialize with an existing handle - takes ownership of the handle + ZENCORE_API [[nodiscard]] bool IsRunning() const; + ZENCORE_API [[nodiscard]] bool IsValid() const; + ZENCORE_API bool Wait(int TimeoutMs = -1); + ZENCORE_API void Terminate(int ExitCode); + ZENCORE_API void Reset(); + inline [[nodiscard]] int Pid() const { return m_Pid; } private: void* m_ProcessHandle = nullptr; diff --git a/zencore/include/zencore/timer.h b/zencore/include/zencore/timer.h index eb284eaee..693b6daaa 100644 --- a/zencore/include/zencore/timer.h +++ b/zencore/include/zencore/timer.h @@ -18,30 +18,26 @@ namespace zen { ZENCORE_API uint64_t GetHifreqTimerValue(); ZENCORE_API uint64_t GetHifreqTimerFrequency(); +ZENCORE_API double GetHifreqTimerToSeconds(); ZENCORE_API uint64_t GetHifreqTimerFrequencySafe(); // May be used during static init class Stopwatch { public: - Stopwatch() : m_StartValue(GetHifreqTimerValue()) {} + inline Stopwatch() : m_StartValue(GetHifreqTimerValue()) {} - inline uint64_t getElapsedTimeMs() { return (GetHifreqTimerValue() - m_StartValue) * 1000 / GetHifreqTimerFrequency(); } + inline uint64_t GetElapsedTimeMs() const { return (GetHifreqTimerValue() - m_StartValue) * 1'000 / GetHifreqTimerFrequency(); } + inline uint64_t GetElapsedTimeUs() const { return (GetHifreqTimerValue() - m_StartValue) * 1'000'000 / GetHifreqTimerFrequency(); } + inline uint64_t GetElapsedTicks() const { return GetHifreqTimerValue() - m_StartValue; } + inline void Reset() { m_StartValue = GetHifreqTimerValue(); } - inline void reset() { m_StartValue = GetHifreqTimerValue(); } + static inline uint64_t GetElapsedTimeMs(uint64_t Ticks) { return Ticks * 1'000 / GetHifreqTimerFrequency(); } + static inline uint64_t GetElapsedTimeUs(uint64_t Ticks) { return Ticks * 1'000'000 / GetHifreqTimerFrequency(); } private: uint64_t m_StartValue; }; -// CPU timers - -inline uint64_t -GetCpuTimerValue() -{ - unsigned int foo; - return __rdtscp(&foo); -} - void timer_forcelink(); // internal } // namespace zen diff --git a/zencore/iobuffer.cpp b/zencore/iobuffer.cpp index a730a316f..244425761 100644 --- a/zencore/iobuffer.cpp +++ b/zencore/iobuffer.cpp @@ -14,6 +14,10 @@ #include <memory.h> #include <system_error> +#if ZEN_USE_MIMALLOC +# include <mimalloc.h> +#endif + #if ZEN_PLATFORM_WINDOWS # include <atlfile.h> #else @@ -36,26 +40,41 @@ IoBufferCore::AllocateBuffer(size_t InSize, size_t Alignment) m_Flags |= kLowLevelAlloc; return VirtualAlloc(nullptr, InSize, MEM_COMMIT, PAGE_READWRITE); } - else #endif // ZEN_PLATFORM_WINDOWS - { - return Memory::Alloc(InSize, Alignment); - } + +#if ZEN_USE_MIMALLOC && 0 + void* Ptr = mi_aligned_alloc(Alignment, RoundUp(InSize, Alignment)); +#else + void* Ptr = Memory::Alloc(InSize, Alignment); +#endif + + ZEN_ASSERT(Ptr); + + return Ptr; } void IoBufferCore::FreeBuffer() { + if (!m_DataPtr) + { + return; + } + #if ZEN_PLATFORM_WINDOWS if (m_Flags & kLowLevelAlloc) { VirtualFree(const_cast<void*>(m_DataPtr), 0, MEM_DECOMMIT); + + return; } - else #endif // ZEN_PLATFORM_WINDOWS - { - return Memory::Free(const_cast<void*>(m_DataPtr)); - } + +#if ZEN_USE_MIMALLOC && 0 + return mi_free(const_cast<void*>(m_DataPtr)); +#else + return Memory::Free(const_cast<void*>(m_DataPtr)); +#endif } ////////////////////////////////////////////////////////////////////////// @@ -436,7 +455,7 @@ IoBufferBuilder::MakeFromTemporaryFile(const path_char_t* FileName) Handle = DataFile.Detach(); #else - int Fd = open(FileName, O_RDONLY); + int Fd = open(FileName, O_RDONLY); if (Fd < 0) { return {}; diff --git a/zencore/memory.cpp b/zencore/memory.cpp index 613b6ba67..da78ae3a8 100644 --- a/zencore/memory.cpp +++ b/zencore/memory.cpp @@ -6,6 +6,7 @@ #ifdef ZEN_PLATFORM_WINDOWS # include <malloc.h> +# include <mimalloc.h> #else # include <cstdlib> #endif @@ -18,8 +19,11 @@ static void* AlignedAllocImpl(size_t size, size_t alignment) { #if ZEN_PLATFORM_WINDOWS - // return _aligned_malloc(size, alignment); // MSVC alternative - return _mm_malloc(size, alignment); +# if ZEN_USE_MIMALLOC && 0 /* this path is not functional */ + return mi_aligned_alloc(alignment, size); +# else + return _aligned_malloc(size, alignment); +# endif #else // posix_memalign(&Ret, Alignment, Size); // Apple, AndroidApi<28 return std::aligned_alloc(alignment, size); @@ -33,8 +37,11 @@ AlignedFreeImpl(void* ptr) return; #if ZEN_PLATFORM_WINDOWS - // _aligned_free(ptr); MSVC alternative - _mm_free(ptr); +# if ZEN_USE_MIMALLOC && 0 /* this path is not functional */ + return mi_free(ptr); +# else + _aligned_free(ptr); +# endif #else // free(ptr) // Apple :) std::free(ptr); diff --git a/zencore/stats.cpp b/zencore/stats.cpp index 9ae2ddd28..34dc2828f 100644 --- a/zencore/stats.cpp +++ b/zencore/stats.cpp @@ -1,10 +1,15 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "zencore/stats.h" -#include <cmath> + +#include <zencore/compactbinarybuilder.h> +#include "zencore/intmath.h" #include "zencore/thread.h" #include "zencore/timer.h" +#include <cmath> +#include <gsl/gsl-lite.hpp> + #if ZEN_WITH_TESTS # include <zencore/testing.h> #endif @@ -13,7 +18,7 @@ // Derived from https://github.com/dln/medida/blob/master/src/medida/stats/ewma.cc // -namespace zen { +namespace zen::metrics { static constinit int kTickIntervalInSeconds = 5; static constinit double kSecondsPerMinute = 60.0; @@ -76,18 +81,18 @@ Meter::TickIfNecessary() if (m_LastTick.compare_exchange_strong(OldTick, NewTick)) { - m_Remain.fetch_add(Age); + m_Remainder.fetch_add(Age); do { - int64_t Remain = m_Remain.load(std::memory_order_relaxed); + int64_t Remain = m_Remainder.load(std::memory_order_relaxed); if (Remain < 0) { return; } - if (m_Remain.compare_exchange_strong(Remain, Remain - CountPerTick)) + if (m_Remainder.compare_exchange_strong(Remain, Remain - CountPerTick)) { Tick(); } @@ -137,7 +142,7 @@ Meter::Rate15() } double -Meter::MeanRate() +Meter::MeanRate() const { const uint64_t Count = m_TotalCount.load(std::memory_order_relaxed); @@ -162,9 +167,354 @@ Meter::Mark(uint64_t Count) ////////////////////////////////////////////////////////////////////////// +// TODO: should consider a cheaper RNG here, this will run for every thread +// that gets created + +thread_local std::mt19937_64 ThreadLocalRng; + +UniformSample::UniformSample(uint32_t ReservoirSize) : m_Values(ReservoirSize) +{ +} + +UniformSample::~UniformSample() +{ +} + +void +UniformSample::Clear() +{ + for (auto& Value : m_Values) + { + Value.store(0); + } + m_SampleCounter = 0; +} + +uint32_t +UniformSample::Size() const +{ + return gsl::narrow_cast<uint32_t>(Min(m_SampleCounter.load(), m_Values.size())); +} + +void +UniformSample::Update(int64_t Value) +{ + const uint64_t Count = m_SampleCounter++; + const uint64_t Size = m_Values.size(); + + if (Count < Size) + { + m_Values[Count] = Value; + } + else + { + // Randomly choose an old entry to potentially replace (the probability + // of replacing an entry diminishes with time) + + std::uniform_int_distribution<uint64_t> UniformDist(0, Count); + uint64_t SampleIndex = UniformDist(ThreadLocalRng); + + if (SampleIndex < Size) + { + m_Values[SampleIndex].store(Value, std::memory_order_release); + } + } +} + +SampleSnapshot +UniformSample::Snapshot() const +{ + uint64_t ValuesSize = Size(); + std::vector<double> Values(ValuesSize); + + for (int i = 0; i < ValuesSize; ++i) + { + Values[i] = double(m_Values[i]); + } + + return SampleSnapshot(std::move(Values)); +} + +////////////////////////////////////////////////////////////////////////// + +Histogram::Histogram(int32_t SampleCount) : m_Sample(SampleCount) +{ +} + +Histogram::~Histogram() +{ +} + +void +Histogram::Clear() +{ + m_Min = m_Max = m_Sum = m_Count = 0; + m_Sample.Clear(); +} + +void +Histogram::Update(int64_t Value) +{ + m_Sample.Update(Value); + + if (m_Count == 0) + { + m_Min = m_Max = Value; + } + else + { + int64_t CurrentMax = m_Max.load(std::memory_order_relaxed); + + while ((CurrentMax < Value) && !m_Max.compare_exchange_weak(CurrentMax, Value)) + { + } + + int64_t CurrentMin = m_Min.load(std::memory_order_relaxed); + + while ((CurrentMin > Value) && !m_Min.compare_exchange_weak(CurrentMin, Value)) + { + } + } + + m_Sum += Value; + ++m_Count; +} + +int64_t +Histogram::Max() const +{ + return m_Max.load(std::memory_order_relaxed); +} + +int64_t +Histogram::Min() const +{ + return m_Min.load(std::memory_order_relaxed); +} + +double +Histogram::Mean() const +{ + return double(m_Sum.load(std::memory_order_relaxed)) / m_Count; +} + +uint64_t +Histogram::Count() const +{ + return m_Count.load(std::memory_order_relaxed); +} + +////////////////////////////////////////////////////////////////////////// + +SampleSnapshot::SampleSnapshot(std::vector<double>&& Values) : m_Values(std::move(Values)) +{ + std::sort(begin(m_Values), end(m_Values)); +} + +SampleSnapshot::~SampleSnapshot() +{ +} + +double +SampleSnapshot::GetQuantileValue(double Quantile) +{ + ZEN_ASSERT((Quantile >= 0.0) && (Quantile <= 1.0)); + + if (m_Values.empty()) + { + return 0.0; + } + + const double Pos = Quantile * (m_Values.size() + 1); + + if (Pos < 1) + { + return m_Values.front(); + } + + if (Pos >= m_Values.size()) + { + return m_Values.back(); + } + + const int32_t Index = (int32_t)Pos; + const double Lower = m_Values[Index - 1]; + const double Upper = m_Values[Index]; + + // Lerp + return Lower + (Pos - std::floor(Pos)) * (Upper - Lower); +} + +const std::vector<double>& +SampleSnapshot::GetValues() const +{ + return m_Values; +} + +////////////////////////////////////////////////////////////////////////// + +OperationTiming::OperationTiming(int32_t SampleCount) : m_Histogram{SampleCount} +{ +} + +OperationTiming::~OperationTiming() +{ +} + +void +OperationTiming::Update(int64_t Duration) +{ + m_Meter.Mark(1); + m_Histogram.Update(Duration); +} + +int64_t +OperationTiming::Max() const +{ + return m_Histogram.Max(); +} + +int64_t +OperationTiming::Min() const +{ + return m_Histogram.Min(); +} + +double +OperationTiming::Mean() const +{ + return m_Histogram.Mean(); +} + +uint64_t +OperationTiming::Count() const +{ + return m_Meter.Count(); +} + +OperationTiming::Scope::Scope(OperationTiming& Outer) : m_Outer(Outer), m_StartTick(GetHifreqTimerValue()) +{ +} + +OperationTiming::Scope::~Scope() +{ + m_Outer.Update(GetHifreqTimerValue() - m_StartTick); +} + +////////////////////////////////////////////////////////////////////////// + +void +EmitSnapshot(std::string_view Tag, OperationTiming& Stat, CbObjectWriter& Cbo) +{ + Cbo.BeginObject(Tag); + + SampleSnapshot Snap = Stat.Snapshot(); + + Cbo << "count" << Stat.Count(); + + Cbo << "rate_mean" << Stat.MeanRate(); + Cbo << "rate_1" << Stat.Rate1() << "rate_5" << Stat.Rate5() << "rate_15" << Stat.Rate15(); + + const double ToSeconds = GetHifreqTimerToSeconds(); + + Cbo << "t_avg" << Stat.Mean() * ToSeconds; + Cbo << "t_min" << Stat.Min() * ToSeconds << "t_max" << Stat.Max() * ToSeconds; + Cbo << "t_p75" << Snap.Get75Percentile() * ToSeconds << "t_p95" << Snap.Get95Percentile() * ToSeconds << "t_p99" + << Snap.Get99Percentile() * ToSeconds << "t_p999" << Snap.Get999Percentile() * ToSeconds; + + Cbo.EndObject(); +} + +void +EmitSnapshot(std::string_view Tag, const Histogram& Stat, CbObjectWriter& Cbo) +{ + Cbo.BeginObject(Tag); + + SampleSnapshot Snap = Stat.Snapshot(); + + Cbo << "count" << Stat.Count() << "avg" << Stat.Mean(); + Cbo << "min" << Stat.Min() << "max" << Stat.Max(); + Cbo << "p75" << Snap.Get75Percentile() << "p95" << Snap.Get95Percentile() << "p99" << Snap.Get99Percentile() << "p999" + << Snap.Get999Percentile(); + + Cbo.EndObject(); +} + +void +EmitSnapshot(std::string_view Tag, Meter& Stat, CbObjectWriter& Cbo) +{ + Cbo.BeginObject(Tag); + + Cbo << "count" << Stat.Count() << "rate_mean" << Stat.MeanRate(); + Cbo << "rate_1" << Stat.Rate1() << "rate_5" << Stat.Rate5() << "rate_15" << Stat.Rate15(); + + Cbo.EndObject(); +} + +////////////////////////////////////////////////////////////////////////// + #if ZEN_WITH_TESTS -TEST_CASE("EWMA") +TEST_CASE("Core.Stats.Histogram") +{ + Histogram Histo{258}; + + SampleSnapshot Snap1 = Histo.Snapshot(); + CHECK_EQ(Snap1.Size(), 0); + CHECK_EQ(Snap1.GetMedian(), 0); + + Histo.Update(1); + CHECK_EQ(Histo.Min(), 1); + CHECK_EQ(Histo.Max(), 1); + + SampleSnapshot Snap2 = Histo.Snapshot(); + CHECK_EQ(Snap2.Size(), 1); + + Histo.Update(2); + CHECK_EQ(Histo.Min(), 1); + CHECK_EQ(Histo.Max(), 2); + + SampleSnapshot Snap3 = Histo.Snapshot(); + CHECK_EQ(Snap3.Size(), 2); + + Histo.Update(-2); + CHECK_EQ(Histo.Min(), -2); + CHECK_EQ(Histo.Max(), 2); + CHECK_EQ(Histo.Mean(), 1 / 3.0); + + SampleSnapshot Snap4 = Histo.Snapshot(); + CHECK_EQ(Snap4.Size(), 3); + CHECK_EQ(Snap4.GetMedian(), 1); + CHECK_EQ(Snap4.Get999Percentile(), 2); + CHECK_EQ(Snap4.GetQuantileValue(0), -2); +} + +TEST_CASE("Core.Stats.UniformSample") +{ + UniformSample Sample1{100}; + + for (int i = 0; i < 100; ++i) + { + for (int j = 1; j <= 100; ++j) + { + Sample1.Update(j); + } + } + + int64_t Sum = 0; + int64_t Count = 0; + + Sample1.IterateValues([&](int64_t Value) { + ++Count; + Sum += Value; + }); + + double Average = double(Sum) / Count; + + CHECK(fabs(Average - 50) < 10); // What's the right test here? The result could vary massively and still be technically correct +} + +TEST_CASE("Core.Stats.EWMA") { SUBCASE("Simple_1") { @@ -262,6 +612,9 @@ TEST_CASE("Meter") [[maybe_unused]] double Rate = Meter1.MeanRate(); } # endif +} + +namespace zen { void stats_forcelink() diff --git a/zencore/timer.cpp b/zencore/timer.cpp index 88ec89cb7..5d30d9b29 100644 --- a/zencore/timer.cpp +++ b/zencore/timer.cpp @@ -42,7 +42,8 @@ InternalGetHifreqTimerFrequency() #endif } -static uint64_t QpcFreq = InternalGetHifreqTimerFrequency(); +uint64_t QpcFreq = InternalGetHifreqTimerFrequency(); +static const double QpcFactor = 1.0 / InternalGetHifreqTimerFrequency(); uint64_t GetHifreqTimerFrequency() @@ -50,6 +51,12 @@ GetHifreqTimerFrequency() return QpcFreq; } +double +GetHifreqTimerToSeconds() +{ + return QpcFactor; +} + uint64_t GetHifreqTimerFrequencySafe() { @@ -73,18 +80,6 @@ timer_forcelink() { } -TEST_CASE("Timer") -{ - uint64_t s0 = GetHifreqTimerValue(); - uint64_t t0 = GetCpuTimerValue(); - zen::Sleep(1000); - uint64_t s1 = GetHifreqTimerValue(); - uint64_t t1 = GetCpuTimerValue(); - // double r = double(t1 - t0) / (s1 - s0); - CHECK_NE(t0, t1); - CHECK_NE(s0, s1); -} - #endif } // namespace zen |