aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/include/zencore/iobuffer.h3
-rw-r--r--src/zencore/include/zencore/memory/memoryarena.h104
-rw-r--r--src/zencore/include/zencore/stats.h356
-rw-r--r--src/zencore/memory/memoryarena.cpp126
-rw-r--r--src/zencore/stats.cpp831
-rw-r--r--src/zencore/trace.cpp1
-rw-r--r--src/zencore/zencore.cpp2
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();