diff options
| author | Stefan Boberg <[email protected]> | 2025-10-22 17:57:29 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-22 17:57:29 +0200 |
| commit | 5c139e2d8a260544bc5e730de0440edbab4b0f03 (patch) | |
| tree | b477208925fe3b373d4833460b90d61a8051cf05 /src/zencore | |
| parent | 5.7.7-pre3 (diff) | |
| download | zen-5c139e2d8a260544bc5e730de0440edbab4b0f03.tar.xz zen-5c139e2d8a260544bc5e730de0440edbab4b0f03.zip | |
add support for OTLP logging/tracing (#599)
- adds `zentelemetry` project which houses new functionality for serializing logs and traces in OpenTelemetry Protocol format (OTLP)
- moved existing stats functionality from `zencore` to `zentelemetry`
- adds `TRefCounted<T>` for vtable-less refcounting
- adds `MemoryArena` class which allows for linear allocation of memory from chunks
- adds `protozero` which is used to encode OTLP protobuf messages
Diffstat (limited to 'src/zencore')
| -rw-r--r-- | src/zencore/include/zencore/iobuffer.h | 3 | ||||
| -rw-r--r-- | src/zencore/include/zencore/memory/memoryarena.h | 104 | ||||
| -rw-r--r-- | src/zencore/include/zencore/stats.h | 356 | ||||
| -rw-r--r-- | src/zencore/memory/memoryarena.cpp | 126 | ||||
| -rw-r--r-- | src/zencore/stats.cpp | 831 | ||||
| -rw-r--r-- | src/zencore/trace.cpp | 1 | ||||
| -rw-r--r-- | src/zencore/zencore.cpp | 2 |
7 files changed, 234 insertions, 1189 deletions
diff --git a/src/zencore/include/zencore/iobuffer.h b/src/zencore/include/zencore/iobuffer.h index 63779407e..1b2d382ee 100644 --- a/src/zencore/include/zencore/iobuffer.h +++ b/src/zencore/include/zencore/iobuffer.h @@ -32,6 +32,7 @@ enum class ZenContentType : uint8_t kPNG = 12, kIcon = 13, kXML = 14, + kProtobuf = 15, kCOUNT }; @@ -73,6 +74,8 @@ ToString(ZenContentType ContentType) return "icon"sv; case ZenContentType::kXML: return "xml"sv; + case ZenContentType::kProtobuf: + return "protobuf"sv; } } diff --git a/src/zencore/include/zencore/memory/memoryarena.h b/src/zencore/include/zencore/memory/memoryarena.h new file mode 100644 index 000000000..551415aac --- /dev/null +++ b/src/zencore/include/zencore/memory/memoryarena.h @@ -0,0 +1,104 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/thread.h> +#include <vector> + +namespace zen { + +/** + * Chunked linear memory allocator + * + * Supports fast allocation of many small objects with minimal overhead. + * All allocations are freed when the arena is destroyed, therefore there + * is no support for individual deallocation. + * + * For convenience, we include operator new/delete overloads below which + * take a MemoryArena reference as a placement argument. + * + * This allocator is thread-safe. + */ +class MemoryArena +{ +public: + MemoryArena() = default; + ~MemoryArena(); + + void* AllocateAligned(size_t ByteCount, size_t align); + void* AllocateAlignedWithOffset(size_t ByteCount, size_t align, size_t offset); + void* Allocate(size_t Size); + const char* DuplicateString(std::string_view Str); + + MemoryArena(const MemoryArena&) = delete; + MemoryArena& operator=(const MemoryArena&) = delete; + +private: + static constexpr size_t ChunkSize = 16 * 1024; + // TODO: should just chain the memory blocks together and avoid this + // vector altogether, saving us a memory allocation + std::vector<uint8_t*> m_Chunks; + uint8_t* m_CurrentChunk = nullptr; + size_t m_CurrentOffset = 0; + RwLock m_Lock; +}; + +// Allocator suitable for use with EASTL + +struct ArenaAlloc +{ + ArenaAlloc(const char* name_opt = nullptr) = delete; + ArenaAlloc(MemoryArena& Arena) : m_Arena(&Arena) {} + + inline void* allocate(size_t bytes, int flags = 0) + { + ZEN_UNUSED(flags); + return m_Arena->Allocate(bytes); + } + + inline void* allocate(size_t bytes, size_t align, size_t offset, int flags = 0) + { + ZEN_UNUSED(flags); + if (offset == 0) + { + return m_Arena->AllocateAligned(bytes, align); + } + else + { + return m_Arena->AllocateAlignedWithOffset(bytes, align, offset); + } + } + + void deallocate(void* p, size_t n) { ZEN_UNUSED(p, n); } + +private: + MemoryArena* m_Arena = nullptr; +}; + +} // namespace zen + +inline void* +operator new(size_t Size, zen::MemoryArena& Arena) +{ + return Arena.Allocate(Size); +} + +inline void +operator delete(void* Ptr, zen::MemoryArena& Arena) +{ + // Arena will clean up all allocations when it's destroyed + ZEN_UNUSED(Ptr, Arena); +} + +inline void* +operator new[](size_t Size, zen::MemoryArena& Arena) +{ + return Arena.Allocate(Size); +} + +inline void +operator delete[](void* Ptr, zen::MemoryArena& Arena) +{ + // Arena will clean up all allocations when it's destroyed + ZEN_UNUSED(Ptr, Arena); +} diff --git a/src/zencore/include/zencore/stats.h b/src/zencore/include/zencore/stats.h deleted file mode 100644 index f232cf2f4..000000000 --- a/src/zencore/include/zencore/stats.h +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "zencore.h" - -#include <zenbase/concepts.h> - -#include <atomic> -#include <string_view> -#include <vector> - -namespace zen { -class CbObjectWriter; -} - -namespace zen::metrics { - -template<typename T> -class Gauge -{ -public: - Gauge() : m_Value{0} {} - - T Value() const { return m_Value; } - void SetValue(T Value) { m_Value = Value; } - -private: - std::atomic<T> m_Value; -}; - -/** Stats counter - * - * A counter is modified by adding or subtracting a value from a current value. - * This would typically be used to track number of requests in flight, number - * of active jobs etc - * - */ -class Counter -{ -public: - inline void SetValue(uint64_t Value) { m_count = Value; } - inline uint64_t Value() const { return m_count; } - - inline void Increment(int64_t AddValue) { m_count.fetch_add(AddValue); } - inline void Decrement(int64_t SubValue) { m_count.fetch_sub(SubValue); } - inline void Clear() { m_count.store(0, std::memory_order_release); } - -private: - std::atomic<uint64_t> m_count{0}; -}; - -/** Exponential Weighted Moving Average - - This is very raw, to use as little state as possible. If we - want to use this more broadly in user code we should perhaps - add a more user-friendly wrapper - */ - -class RawEWMA -{ -public: - /// <summary> - /// Update EWMA with new measure - /// </summary> - /// <param name="Alpha">Smoothing factor (between 0 and 1)</param> - /// <param name="Interval">Elapsed time since last</param> - /// <param name="Count">Value</param> - /// <param name="IsInitialUpdate">Whether this is the first update or not</param> - void Tick(double Alpha, uint64_t Interval, uint64_t Count, bool IsInitialUpdate); - double Rate() const; - -private: - std::atomic<double> m_Rate = 0; -}; - -/// <summary> -/// Tracks rate of events over time (i.e requests/sec), using -/// exponential moving averages -/// </summary> -class Meter -{ -public: - Meter(); - ~Meter(); - - 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_Remainder{0}; // Tracks the "modulo" of tick time - bool m_IsFirstTick = true; - RawEWMA m_RateM1; - RawEWMA m_RateM5; - RawEWMA m_RateM15; - - void TickIfNecessary(); - 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<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(); - - void Stop(); - void Cancel(); - - private: - OperationTiming& m_Outer; - uint64_t m_StartTick; - }; - -private: - Meter m_Meter; - Histogram m_Histogram; -}; - -struct MeterSnapshot -{ - uint64_t Count; - double MeanRate; - double Rate1; - double Rate5; - double Rate15; -}; - -struct HistogramSnapshot -{ - double Count; - double Avg; - double Min; - double Max; - double P75; - double P95; - double P99; - double P999; -}; - -struct StatsSnapshot -{ - MeterSnapshot Meter; - HistogramSnapshot Histogram; -}; - -struct RequestStatsSnapshot -{ - StatsSnapshot Requests; - StatsSnapshot Bytes; -}; - -/** Metrics for network requests - - Aggregates tracking of duration, payload sizes into a single - class - - */ -class RequestStats -{ -public: - RequestStats(int32_t SampleCount = 514); - ~RequestStats(); - - void Update(int64_t Duration, int64_t Bytes); - uint64_t Count() const; - - // Timing - - int64_t MaxDuration() const { return m_BytesHistogram.Max(); } - int64_t MinDuration() const { return m_BytesHistogram.Min(); } - double MeanDuration() const { return m_BytesHistogram.Mean(); } - SampleSnapshot DurationSnapshot() const { return m_RequestTimeHistogram.Snapshot(); } - double Rate1() { return m_RequestMeter.Rate1(); } - double Rate5() { return m_RequestMeter.Rate5(); } - double Rate15() { return m_RequestMeter.Rate15(); } - double MeanRate() const { return m_RequestMeter.MeanRate(); } - - // Bytes - - int64_t MaxBytes() const { return m_BytesHistogram.Max(); } - int64_t MinBytes() const { return m_BytesHistogram.Min(); } - double MeanBytes() const { return m_BytesHistogram.Mean(); } - SampleSnapshot BytesSnapshot() const { return m_BytesHistogram.Snapshot(); } - double ByteRate1() { return m_BytesMeter.Rate1(); } - double ByteRate5() { return m_BytesMeter.Rate5(); } - double ByteRate15() { return m_BytesMeter.Rate15(); } - double ByteMeanRate() const { return m_BytesMeter.MeanRate(); } - - struct Scope - { - Scope(RequestStats& Outer, int64_t Bytes); - ~Scope(); - - void SetBytes(int64_t Bytes) { m_Bytes = Bytes; } - void Stop(); - void Cancel(); - - private: - RequestStats& m_Outer; - uint64_t m_StartTick; - int64_t m_Bytes; - }; - - void EmitSnapshot(std::string_view Tag, CbObjectWriter& Cbo); - - RequestStatsSnapshot Snapshot(); - -private: - static StatsSnapshot GetSnapshot(Meter& M, Histogram& H, double ConversionFactor) - { - SampleSnapshot Snap = H.Snapshot(); - return StatsSnapshot{ - .Meter = {.Count = M.Count(), .MeanRate = M.MeanRate(), .Rate1 = M.Rate1(), .Rate5 = M.Rate5(), .Rate15 = M.Rate15()}, - .Histogram = {.Count = H.Count() * ConversionFactor, - .Avg = H.Mean() * ConversionFactor, - .Min = H.Min() * ConversionFactor, - .Max = H.Max() * ConversionFactor, - .P75 = Snap.Get75Percentile() * ConversionFactor, - .P95 = Snap.Get95Percentile() * ConversionFactor, - .P99 = Snap.Get99Percentile() * ConversionFactor, - .P999 = Snap.Get999Percentile() * ConversionFactor}}; - } - - Meter m_RequestMeter; - Meter m_BytesMeter; - Histogram m_RequestTimeHistogram; - Histogram m_BytesHistogram; -}; - -void EmitSnapshot(std::string_view Tag, OperationTiming& Stat, CbObjectWriter& Cbo); -void EmitSnapshot(std::string_view Tag, const Histogram& Stat, CbObjectWriter& Cbo, double ConversionFactor); -void EmitSnapshot(std::string_view Tag, Meter& Stat, CbObjectWriter& Cbo); - -void EmitSnapshot(const Histogram& Stat, CbObjectWriter& Cbo, double ConversionFactor); - -void EmitSnapshot(std::string_view Tag, const MeterSnapshot& Snapshot, CbObjectWriter& Cbo); -void EmitSnapshot(std::string_view Tag, const HistogramSnapshot& Snapshot, CbObjectWriter& Cbo); -void EmitSnapshot(std::string_view Tag, const StatsSnapshot& Snapshot, CbObjectWriter& Cbo); -void EmitSnapshot(std::string_view Tag, const RequestStatsSnapshot& Snapshot, CbObjectWriter& Cbo); - -} // namespace zen::metrics - -namespace zen { - -extern void stats_forcelink(); - -} // namespace zen diff --git a/src/zencore/memory/memoryarena.cpp b/src/zencore/memory/memoryarena.cpp new file mode 100644 index 000000000..9c907a66d --- /dev/null +++ b/src/zencore/memory/memoryarena.cpp @@ -0,0 +1,126 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zencore/memory/memoryarena.h> + +namespace zen { + +MemoryArena::~MemoryArena() +{ + for (auto Chunk : m_Chunks) + delete[] Chunk; +} + +void* +MemoryArena::AllocateAligned(size_t ByteCount, size_t align) +{ + if (ByteCount == 0) + { + return nullptr; + } + + void* Ptr = nullptr; + + m_Lock.WithExclusiveLock([&] { + size_t AlignedOffset = (m_CurrentOffset + (align - 1)) & ~(align - 1); + + if (m_CurrentChunk == nullptr || AlignedOffset + ByteCount > ChunkSize) + { + uint8_t* NewChunk = new uint8_t[ChunkSize]; + if (!NewChunk) + { + return; + } + + m_Chunks.push_back(NewChunk); + m_CurrentChunk = NewChunk; + AlignedOffset = 0; + } + + Ptr = m_CurrentChunk + AlignedOffset; + m_CurrentOffset = AlignedOffset + ByteCount; + }); + + return Ptr; +} + +void* +MemoryArena::AllocateAlignedWithOffset(size_t ByteCount, size_t align, size_t offset) +{ + if (ByteCount == 0) + { + return nullptr; + } + + void* Ptr = nullptr; + + m_Lock.WithExclusiveLock([&] { + size_t AlignedOffset = (m_CurrentOffset + (align - 1) + offset) & ~(align - 1); + + if (m_CurrentChunk == nullptr || AlignedOffset + ByteCount > ChunkSize) + { + uint8_t* NewChunk = new uint8_t[ChunkSize]; + if (!NewChunk) + { + return; + } + + m_Chunks.push_back(NewChunk); + m_CurrentChunk = NewChunk; + AlignedOffset = offset; + } + + Ptr = m_CurrentChunk + AlignedOffset; + m_CurrentOffset = AlignedOffset + ByteCount; + }); + + return Ptr; +} + +void* +MemoryArena::Allocate(size_t Size) +{ + if (Size == 0) + { + return nullptr; + } + + void* Ptr = nullptr; + constexpr size_t Alignment = alignof(std::max_align_t); + + m_Lock.WithExclusiveLock([&] { + size_t AlignedOffset = (m_CurrentOffset + Alignment - 1) & ~(Alignment - 1); + + if (m_CurrentChunk == nullptr || AlignedOffset + Size > ChunkSize) + { + uint8_t* NewChunk = new uint8_t[ChunkSize]; + if (!NewChunk) + { + return; + } + + m_Chunks.push_back(NewChunk); + m_CurrentChunk = NewChunk; + AlignedOffset = 0; + } + + Ptr = m_CurrentChunk + AlignedOffset; + m_CurrentOffset = AlignedOffset + Size; + }); + + return Ptr; +} + +const char* +MemoryArena::DuplicateString(std::string_view Str) +{ + const size_t Len = Str.size(); + char* NewStr = static_cast<char*>(Allocate(Len + 1)); + if (NewStr) + { + memcpy(NewStr, Str.data(), Len); + NewStr[Len] = '\0'; + } + return NewStr; +} + +} // namespace zen diff --git a/src/zencore/stats.cpp b/src/zencore/stats.cpp deleted file mode 100644 index 8a424c5ad..000000000 --- a/src/zencore/stats.cpp +++ /dev/null @@ -1,831 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "zencore/stats.h" - -#include <zencore/compactbinarybuilder.h> -#include <zencore/intmath.h> -#include <zencore/memory/llm.h> -#include <zencore/memory/tagtrace.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 - -// -// Derived from https://github.com/dln/medida/blob/master/src/medida/stats/ewma.cc -// - -namespace zen::metrics { - -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 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 const uint64_t CountPerTick = GetHifreqTimerFrequencySafe() * kTickIntervalInSeconds; -static const uint64_t CountPerSecond = GetHifreqTimerFrequencySafe(); - -////////////////////////////////////////////////////////////////////////// - -void -RawEWMA::Tick(double Alpha, uint64_t Interval, uint64_t Count, bool IsInitialUpdate) -{ - const double InstantRate = double(Count) / Interval; - - if (IsInitialUpdate) - { - m_Rate.store(InstantRate, std::memory_order_release); - } - else - { - double Delta = Alpha * (InstantRate - m_Rate); - -#if defined(__cpp_lib_atomic_float) - m_Rate.fetch_add(Delta); -#else - double Value = m_Rate.load(std::memory_order_acquire); - double Next; - do - { - Next = Value + Delta; - } while (!m_Rate.compare_exchange_weak(Value, Next, std::memory_order_relaxed)); -#endif - } -} - -double -RawEWMA::Rate() const -{ - 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_Remainder.fetch_add(Age); - - do - { - int64_t Remain = m_Remainder.load(std::memory_order_relaxed); - - if (Remain < 0) - { - return; - } - - if (m_Remainder.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 -{ - 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); -} - -////////////////////////////////////////////////////////////////////////// - -uint64_t -rol64(uint64_t x, int k) -{ - return (x << k) | (x >> (64 - k)); -} - -struct xoshiro256ss_state -{ - uint64_t s[4]; -}; - -uint64_t -xoshiro256ss(struct xoshiro256ss_state* state) -{ - uint64_t* s = state->s; - uint64_t const result = rol64(s[1] * 5, 7) * 9; - uint64_t const t = s[1] << 17; - - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - - s[2] ^= t; - s[3] = rol64(s[3], 45); - - return result; -} - -class xoshiro256 -{ -public: - uint64_t operator()() { return xoshiro256ss(&m_State); } - static constexpr uint64_t min() { return 0; } - static constexpr uint64_t max() { return ~(0ull); } - -private: - xoshiro256ss_state m_State{0xf0fefaf9, 0xbeeb5238, 0x48472397, 0x58858558}; -}; - -thread_local xoshiro256 ThreadLocalRng; - -////////////////////////////////////////////////////////////////////////// - -UniformSample::UniformSample(uint32_t ReservoirSize) -{ - ZEN_MEMSCOPE(ELLMTag::Metrics); - m_Values = std::vector<std::atomic<int64_t>>(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) - - const uint64_t SampleIndex = ThreadLocalRng() % Count; - - if (SampleIndex < Size) - { - m_Values[SampleIndex].store(Value, std::memory_order_release); - } - } -} - -SampleSnapshot -UniformSample::Snapshot() const -{ - ZEN_MEMSCOPE(ELLMTag::Metrics); - - uint64_t ValuesSize = Size(); - std::vector<double> Values(ValuesSize); - - for (int i = 0, n = int(ValuesSize); i < n; ++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 -{ - if (m_Count) - { - return double(m_Sum.load(std::memory_order_relaxed)) / m_Count; - } - else - { - return 0.0; - } -} - -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() -{ - Stop(); -} - -void -OperationTiming::Scope::Stop() -{ - if (m_StartTick != 0) - { - m_Outer.Update(GetHifreqTimerValue() - m_StartTick); - m_StartTick = 0; - } -} - -void -OperationTiming::Scope::Cancel() -{ - m_StartTick = 0; -} - -////////////////////////////////////////////////////////////////////////// - -RequestStats::RequestStats(int32_t SampleCount) : m_RequestTimeHistogram{SampleCount}, m_BytesHistogram{SampleCount} -{ -} - -RequestStats::~RequestStats() -{ -} - -void -RequestStats::Update(int64_t Duration, int64_t Bytes) -{ - m_RequestMeter.Mark(1); - m_RequestTimeHistogram.Update(Duration); - - m_BytesMeter.Mark(Bytes); - m_BytesHistogram.Update(Bytes); -} - -uint64_t -RequestStats::Count() const -{ - return m_RequestMeter.Count(); -} - -RequestStats::Scope::Scope(RequestStats& Outer, int64_t Bytes) : m_Outer(Outer), m_StartTick(GetHifreqTimerValue()), m_Bytes(Bytes) -{ -} - -RequestStats::Scope::~Scope() -{ - Stop(); -} - -void -RequestStats::Scope::Stop() -{ - if (m_StartTick != 0) - { - m_Outer.Update(GetHifreqTimerValue() - m_StartTick, m_Bytes); - m_StartTick = 0; - } -} - -void -RequestStats::Scope::Cancel() -{ - m_StartTick = 0; -} - -////////////////////////////////////////////////////////////////////////// - -void -EmitSnapshot(Meter& Stat, CbObjectWriter& Cbo) -{ - Cbo << "count" << Stat.Count(); - Cbo << "rate_mean" << Stat.MeanRate(); - Cbo << "rate_1" << Stat.Rate1() << "rate_5" << Stat.Rate5() << "rate_15" << Stat.Rate15(); -} - -void -RequestStats::EmitSnapshot(std::string_view Tag, CbObjectWriter& Cbo) -{ - Cbo.BeginObject(Tag); - - Cbo.BeginObject("requests"); - metrics::EmitSnapshot(m_RequestMeter, Cbo); - metrics::EmitSnapshot(m_RequestTimeHistogram, Cbo, GetHifreqTimerToSeconds()); - Cbo.EndObject(); - - Cbo.BeginObject("bytes"); - metrics::EmitSnapshot(m_BytesMeter, Cbo); - metrics::EmitSnapshot(m_BytesHistogram, Cbo, 1.0); - Cbo.EndObject(); - - Cbo.EndObject(); -} - -RequestStatsSnapshot -RequestStats::Snapshot() -{ - const double ToSeconds = GetHifreqTimerToSeconds(); - - return RequestStatsSnapshot{.Requests = GetSnapshot(m_RequestMeter, m_RequestTimeHistogram, ToSeconds), - .Bytes = GetSnapshot(m_BytesMeter, m_BytesHistogram, 1.0)}; -} - -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, double ConversionFactor) -{ - Cbo.BeginObject(Tag); - EmitSnapshot(Stat, Cbo, ConversionFactor); - Cbo.EndObject(); -} - -void -EmitSnapshot(const Histogram& Stat, CbObjectWriter& Cbo, double ConversionFactor) -{ - SampleSnapshot Snap = Stat.Snapshot(); - - Cbo << "count" << Stat.Count() * ConversionFactor << "avg" << Stat.Mean() * ConversionFactor; - Cbo << "min" << Stat.Min() * ConversionFactor << "max" << Stat.Max() * ConversionFactor; - Cbo << "p75" << Snap.Get75Percentile() * ConversionFactor << "p95" << Snap.Get95Percentile() * ConversionFactor << "p99" - << Snap.Get99Percentile() * ConversionFactor << "p999" << Snap.Get999Percentile() * ConversionFactor; -} - -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(); -} - -void -EmitSnapshot(const MeterSnapshot& Snapshot, CbObjectWriter& Cbo) -{ - Cbo << "count" << Snapshot.Count; - Cbo << "rate_mean" << Snapshot.MeanRate; - Cbo << "rate_1" << Snapshot.Rate1 << "rate_5" << Snapshot.Rate5 << "rate_15" << Snapshot.Rate15; -} - -void -EmitSnapshot(const HistogramSnapshot& Snapshot, CbObjectWriter& Cbo) -{ - Cbo << "t_count" << Snapshot.Count << "t_avg" << Snapshot.Avg; - Cbo << "t_min" << Snapshot.Min << "t_max" << Snapshot.Max; - Cbo << "t_p75" << Snapshot.P75 << "t_p95" << Snapshot.P95 << "t_p99" << Snapshot.P999; -} - -void -EmitSnapshot(std::string_view Tag, const StatsSnapshot& Snapshot, CbObjectWriter& Cbo) -{ - Cbo.BeginObject(Tag); - EmitSnapshot(Snapshot.Meter, Cbo); - EmitSnapshot(Snapshot.Histogram, Cbo); - Cbo.EndObject(); -} - -void -EmitSnapshot(std::string_view Tag, const RequestStatsSnapshot& Snapshot, CbObjectWriter& Cbo) -{ - if (Snapshot.Requests.Meter.Count == 0) - { - return; - } - Cbo.BeginObject(Tag); - EmitSnapshot("request", Snapshot.Requests, Cbo); - EmitSnapshot("bytes", Snapshot.Bytes, Cbo); - Cbo.EndObject(); -} - -////////////////////////////////////////////////////////////////////////// - -#if ZEN_WITH_TESTS - -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") - { - 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") - { - 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); - - 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) - { - 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(fabs(Ewma1.Rate() - 20) < 0.1); - CHECK(fabs(Ewma5.Rate() - 20) < 0.1); - - 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 -} - -namespace zen { - -void -stats_forcelink() -{ -} - -#endif - -} // namespace zen::metrics diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index fe8fb9a5d..87035554f 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -9,6 +9,7 @@ # include <zencore/logging.h> # define TRACE_IMPLEMENT 1 +# undef _WINSOCK_DEPRECATED_NO_WARNINGS # include <zencore/trace.h> # include <zencore/memory/fmalloc.h> diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp index b78991918..4ff79edc7 100644 --- a/src/zencore/zencore.cpp +++ b/src/zencore/zencore.cpp @@ -26,7 +26,6 @@ #include <zencore/parallelwork.h> #include <zencore/process.h> #include <zencore/sha1.h> -#include <zencore/stats.h> #include <zencore/stream.h> #include <zencore/string.h> #include <zencore/thread.h> @@ -267,7 +266,6 @@ zencore_forcelinktests() zen::process_forcelink(); zen::refcount_forcelink(); zen::sha1_forcelink(); - zen::stats_forcelink(); zen::stream_forcelink(); zen::string_forcelink(); zen::thread_forcelink(); |