25#ifndef JOIN_CORE_STATISTICS_HPP
26#define JOIN_CORE_STATISTICS_HPP
48 template <
class ClockPolicy>
52 using Duration =
typename ClockPolicy::Duration;
61 : _countsMem (_countsSize)
62 , _counts (static_cast<
std::atomic<uint64_t>*> (_countsMem.get ()))
65 for (
int i = 0; i < _countsLen; ++i)
67 new (&_counts[i]) std::atomic<uint64_t> (0);
102 for (
int i = 0; i < _countsLen; ++i)
104 _counts[i].~atomic<uint64_t> ();
112 const std::string&
name () const noexcept
123 return ClockPolicy::now ();
133 static_cast<uint64_t
> (std::chrono::duration_cast<Duration> (ClockPolicy::now () - startTime).count ());
135 _sum.fetch_add (ns, std::memory_order_relaxed);
136 _last.store (ns, std::memory_order_relaxed);
138 auto prev = _min.load (std::memory_order_relaxed);
140 !_min.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
143 prev = _max.load (std::memory_order_relaxed);
145 !_max.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
148 _counts[countsIndex (ns)].fetch_add (1, std::memory_order_relaxed);
149 _count.fetch_add (1, std::memory_order_release);
157 _count.store (0, std::memory_order_relaxed);
158 _last.store (0, std::memory_order_relaxed);
159 _min.store (std::numeric_limits<uint64_t>::max (), std::memory_order_relaxed);
160 _max.store (0, std::memory_order_relaxed);
161 _sum.store (0, std::memory_order_relaxed);
162 for (
int i = 0; i < _countsLen; ++i)
164 _counts[i].store (0, std::memory_order_relaxed);
174 return _count.load (std::memory_order_acquire);
183 if (_count.load (std::memory_order_acquire) == 0)
187 return Duration (_last.load (std::memory_order_relaxed));
196 if (_count.load (std::memory_order_acquire) == 0)
200 return Duration (_min.load (std::memory_order_relaxed));
209 if (_count.load (std::memory_order_acquire) == 0)
213 return Duration (_max.load (std::memory_order_relaxed));
220 std::chrono::duration<double, std::nano>
mean () const noexcept
222 const auto count = _count.load (std::memory_order_acquire);
225 return std::chrono::duration<double, std::nano> (0.0);
227 return std::chrono::duration<double, std::nano> (
228 static_cast<double> (_sum.load (std::memory_order_relaxed)) /
static_cast<double> (
count));
237 const auto count = _count.load (std::memory_order_acquire);
238 const auto sum = _sum.load (std::memory_order_acquire);
240 if (
count == 0 || sum == 0)
245 return (
static_cast<double> (
count) * 1e9) /
static_cast<double> (sum);
255 const uint64_t total = _count.load (std::memory_order_acquire);
261 const uint64_t target =
static_cast<uint64_t
> (p / 100.0 *
static_cast<double> (total));
262 uint64_t cumulative = 0;
264 for (
int i = 0; i < _countsLen; ++i)
266 cumulative += _counts[i].load (std::memory_order_relaxed);
268 if (cumulative > target)
270 return Duration (
static_cast<typename Duration::rep
> (bucketUpperBound (i)));
274 return Duration (
static_cast<typename Duration::rep
> (_maxTrackableValue));
283 int mbind (
int numa)
const noexcept
285 return _countsMem.mbind (numa);
295 return _countsMem.
mlock ();
304 static int hdrBucketIndex (uint64_t v)
noexcept
306 const int pow2ceiling = 64 - __builtin_clzll (v |
static_cast<uint64_t
> (_subBucketCount - 1));
307 return std::max (0, pow2ceiling - (_subBucketHalfCountMagnitude + 1));
315 static int countsIndex (uint64_t ns)
noexcept
322 const int bi = hdrBucketIndex (ns);
323 const int si =
static_cast<int> (ns >> bi);
324 const int idx = (bi + 1) * _subBucketHalfCount + si - _subBucketHalfCount;
339 static uint64_t bucketUpperBound (
int idx)
noexcept
341 if (idx >= _overflowIdx)
343 return _maxTrackableValue;
346 const int bi = std::max (0, idx / _subBucketHalfCount - 1);
347 const int si = idx - bi * _subBucketHalfCount;
349 return static_cast<uint64_t
> (si + 1) << bi;
353 static constexpr int _subBucketHalfCountMagnitude = 7;
356 static constexpr int _subBucketCount = 1 << (_subBucketHalfCountMagnitude + 1);
359 static constexpr int _subBucketHalfCount = _subBucketCount >> 1;
362 static constexpr int _bucketCount = 30;
365 static constexpr int _buckets = (_bucketCount + 1) * _subBucketHalfCount;
368 static constexpr int _countsLen = _buckets + 1;
371 static constexpr int _overflowIdx = _buckets;
374 static constexpr uint64_t _maxTrackableValue =
static_cast<uint64_t
> (_subBucketCount) << (_bucketCount - 1);
377 static constexpr uint64_t _countsSize =
static_cast<uint64_t
> (_countsLen) *
sizeof (std::atomic<uint64_t>);
383 std::atomic<uint64_t>*
const _counts;
386 alignas (64) std::atomic_uint64_t _count{0};
389 alignas (64) std::atomic_uint64_t _last{0};
392 alignas (64) std::atomic_uint64_t _min{std::numeric_limits<uint64_t>::max ()};
395 alignas (64) std::atomic_uint64_t _max{0};
398 alignas (64) std::atomic_uint64_t _sum{0};
401 const std::string _name;
430 static int idx = std::ios_base::xalloc ();
440 static int idx = std::ios_base::xalloc ();
467 inline std::ostream&
nsec (std::ostream& out)
478 inline std::ostream&
usec (std::ostream& out)
489 inline std::ostream&
msec (std::ostream& out)
500 inline std::ostream&
sec (std::ostream& out)
511 inline std::ostream&
ops (std::ostream& out)
522 inline std::ostream&
kops (std::ostream& out)
533 inline std::ostream&
mops (std::ostream& out)
544 inline std::ostream&
gops (std::ostream& out)
556 template <
class ClockPolicy>
560 const long lscale = [&] {
562 return s == 0 ? 1L : s;
565 const double dlscale =
static_cast<double> (lscale);
566 const char* lunit =
"ns";
567 if (lscale == 1'000'000'000)
571 else if (lscale == 1'000'000)
575 else if (lscale == 1'000)
581 const long tscale = [&] {
583 return s == 0 ? 1L : s;
586 const double dtscale =
static_cast<double> (tscale);
587 const char* tunit =
"ops/s";
588 if (tscale == 1'000'000'000)
592 else if (tscale == 1'000'000)
596 else if (tscale == 1'000)
602 const auto count = statistics.
count ();
603 const auto min = statistics.
min ();
604 const auto mean = statistics.
mean ();
605 const auto max = statistics.
max ();
607 const auto p50 = statistics.
percentile (50.0);
608 const auto p90 = statistics.
percentile (90.0);
609 const auto p99 = statistics.
percentile (99.0);
611 auto printLatCol = [&] (
double v) {
612 std::ostringstream ss;
613 ss << std::fixed << std::setprecision (out.precision ()) << v <<
" (" << lunit <<
")";
616 std::ostringstream oss;
617 oss << std::fixed << std::setprecision (out.precision ()) << thr / dtscale <<
" (" << tunit <<
")";
620 printLatCol (
static_cast<double> (min.count ()) / dlscale);
621 printLatCol (mean.count () / dlscale);
622 printLatCol (
static_cast<double> (max.count ()) / dlscale);
623 printLatCol (
static_cast<double> (p50.count ()) / dlscale);
624 printLatCol (
static_cast<double> (p90.count ()) / dlscale);
625 printLatCol (
static_cast<double> (p99.count ()) / dlscale);
633 template <
typename Statistics>
644 : _statistics (stats)
645 , _start (_statistics.start ())
680 _statistics.stop (_start);
685 Statistics& _statistics;
lock-free, multi-producer-safe performance statistics collector.
Definition statistics.hpp:50
~BasicStats() noexcept
destroy instance.
Definition statistics.hpp:100
BasicStats(BasicStats &&other)=delete
move constructor.
std::chrono::duration< double, std::nano > mean() const noexcept
arithmetic mean of all completed intervals.
Definition statistics.hpp:220
Duration last() const noexcept
duration of the most recently completed interval.
Definition statistics.hpp:181
int mlock() const noexcept
lock histogram memory in RAM.
Definition statistics.hpp:293
typename ClockPolicy::TimePoint TimePoint
Definition statistics.hpp:53
TimePoint start() const noexcept
mark the beginning of a measured interval.
Definition statistics.hpp:121
void stop(TimePoint startTime) noexcept
mark the end of a measured interval and update all aggregates.
Definition statistics.hpp:130
BasicStats(const std::string &name={})
create instance.
Definition statistics.hpp:60
typename ClockPolicy::Duration Duration
Definition statistics.hpp:52
const std::string & name() const noexcept
get metric name.
Definition statistics.hpp:112
BasicStats(const BasicStats &other)=delete
copy constructor.
Duration min() const noexcept
minimum duration observed across all completed intervals.
Definition statistics.hpp:194
double throughput() const noexcept
operations per second.
Definition statistics.hpp:235
void reset() noexcept
reset all accumulators to their initial state.
Definition statistics.hpp:155
BasicStats & operator=(const BasicStats &other)=delete
copy assignment.
Duration max() const noexcept
maximum duration observed across all completed intervals.
Definition statistics.hpp:207
Duration percentile(double p) const noexcept
compute the requested percentile from the HDR histogram.
Definition statistics.hpp:253
uint64_t count() const noexcept
total number of completed intervals.
Definition statistics.hpp:172
int mlock() const noexcept
lock memory in RAM.
Definition memory.hpp:470
RAII guard that automatically calls start() on construction and stop() on destruction.
Definition statistics.hpp:635
ScopedStats(ScopedStats &&other)=delete
move constructor.
~ScopedStats() noexcept
destructor.
Definition statistics.hpp:678
ScopedStats(const ScopedStats &other)=delete
copy constructor.
ScopedStats & operator=(const ScopedStats &other)=delete
copy assignment.
ScopedStats(Statistics &stats) noexcept
construct the guard and immediately call start() on stats.
Definition statistics.hpp:643
typename Statistics::TimePoint TimePoint
Definition statistics.hpp:637
constexpr int colCount
width of the sample count column.
Definition statistics.hpp:413
int throughputScaleIndex() noexcept
returns the xalloc index used to store the throughput scale on a stream.
Definition statistics.hpp:438
constexpr int colLatency
width of the latency columns.
Definition statistics.hpp:419
constexpr int colTotal
total table width.
Definition statistics.hpp:422
int latencyScaleIndex() noexcept
returns the xalloc index used to store the latency scale on a stream.
Definition statistics.hpp:428
constexpr int colThroughput
width of the throughput column.
Definition statistics.hpp:416
constexpr int colMetric
width of the metric name column.
Definition statistics.hpp:410
Definition acceptor.hpp:32
std::ostream & kops(std::ostream &out)
set throughput display unit to Kops/sec.
Definition statistics.hpp:522
std::ostream & msec(std::ostream &out)
set latency display unit to milliseconds.
Definition statistics.hpp:489
std::ostream & sec(std::ostream &out)
set latency display unit to seconds.
Definition statistics.hpp:500
std::ostream & ops(std::ostream &out)
set throughput display unit to ops/sec.
Definition statistics.hpp:511
std::ostream & gops(std::ostream &out)
set throughput display unit to Gops/sec.
Definition statistics.hpp:544
std::ostream & operator<<(std::ostream &os, const BasicLinkLayerEndpoint< Protocol > &endpoint)
push endpoint representation into a stream.
Definition endpoint.hpp:221
std::ostream & nsec(std::ostream &out)
set latency display unit to nanoseconds.
Definition statistics.hpp:467
std::ostream & usec(std::ostream &out)
set latency display unit to microseconds.
Definition statistics.hpp:478
std::ostream & mops(std::ostream &out)
set throughput display unit to Mops/sec.
Definition statistics.hpp:533
std::ostream & statsHeader(std::ostream &out)
print the statistics table header to a stream.
Definition statistics.hpp:450
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51