join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
statistics.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_STATISTICS_HPP
26#define JOIN_CORE_STATISTICS_HPP
27
28// libjoin.
29#include <join/memory.hpp>
30#include <join/clock.hpp>
31
32// C++.
33#include <algorithm>
34#include <iomanip>
35#include <ostream>
36#include <sstream>
37#include <limits>
38#include <atomic>
39
40// C.
41#include <cstdint>
42
43namespace join
44{
48 template <class ClockPolicy>
50 {
51 public:
52 using Duration = typename ClockPolicy::Duration;
53 using TimePoint = typename ClockPolicy::TimePoint;
54
59 explicit BasicStats (const std::string& name = {})
60 : _countsMem (_countsSize)
61 , _counts (static_cast<std::atomic<uint64_t>*> (_countsMem.get ()))
62 , _name (name)
63 {
64 for (int i = 0; i < _countsLen; ++i)
65 {
66 new (&_counts[i]) std::atomic<uint64_t> (0);
67 }
68 }
69
74 BasicStats (const BasicStats& other) = delete;
75
81 BasicStats& operator= (const BasicStats& other) = delete;
82
87 BasicStats (BasicStats&& other) = delete;
88
94 BasicStats& operator= (BasicStats&& other) = delete;
95
99 ~BasicStats () noexcept
100 {
101 for (int i = 0; i < _countsLen; ++i)
102 {
103 _counts[i].~atomic<uint64_t> ();
104 }
105 }
106
111 const std::string& name () const noexcept
112 {
113 return _name;
114 }
115
120 TimePoint start () const noexcept
121 {
122 return ClockPolicy::now ();
123 }
124
129 void stop (TimePoint startTime) noexcept
130 {
131 const uint64_t ns =
132 static_cast<uint64_t> (std::chrono::duration_cast<Duration> (ClockPolicy::now () - startTime).count ());
133
134 _sum.fetch_add (ns, std::memory_order_relaxed);
135 _last.store (ns, std::memory_order_relaxed);
136
137 auto prev = _min.load (std::memory_order_relaxed);
138 while (ns < prev &&
139 !_min.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
140 ;
141
142 prev = _max.load (std::memory_order_relaxed);
143 while (ns > prev &&
144 !_max.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
145 ;
146
147 _counts[countsIndex (ns)].fetch_add (1, std::memory_order_relaxed);
148 _count.fetch_add (1, std::memory_order_release);
149 }
150
154 void reset () noexcept
155 {
156 _count.store (0, std::memory_order_relaxed);
157 _last.store (0, std::memory_order_relaxed);
158 _min.store (std::numeric_limits<uint64_t>::max (), std::memory_order_relaxed);
159 _max.store (0, std::memory_order_relaxed);
160 _sum.store (0, std::memory_order_relaxed);
161 for (int i = 0; i < _countsLen; ++i)
162 {
163 _counts[i].store (0, std::memory_order_relaxed);
164 }
165 }
166
171 uint64_t count () const noexcept
172 {
173 return _count.load (std::memory_order_acquire);
174 }
175
180 Duration last () const noexcept
181 {
182 if (_count.load (std::memory_order_acquire) == 0)
183 {
184 return Duration (0);
185 }
186 return Duration (_last.load (std::memory_order_relaxed));
187 }
188
193 Duration min () const noexcept
194 {
195 if (_count.load (std::memory_order_acquire) == 0)
196 {
197 return Duration (0);
198 }
199 return Duration (_min.load (std::memory_order_relaxed));
200 }
201
206 Duration max () const noexcept
207 {
208 if (_count.load (std::memory_order_acquire) == 0)
209 {
210 return Duration (0);
211 }
212 return Duration (_max.load (std::memory_order_relaxed));
213 }
214
219 std::chrono::duration<double, std::nano> mean () const noexcept
220 {
221 const auto count = _count.load (std::memory_order_acquire);
222 if (count == 0)
223 {
224 return std::chrono::duration<double, std::nano> (0.0);
225 }
226 return std::chrono::duration<double, std::nano> (
227 static_cast<double> (_sum.load (std::memory_order_relaxed)) / static_cast<double> (count));
228 }
229
234 double throughput () const noexcept
235 {
236 const auto count = _count.load (std::memory_order_acquire);
237 const auto sum = _sum.load (std::memory_order_acquire);
238
239 if (count == 0 || sum == 0)
240 {
241 return 0.0;
242 }
243
244 return (static_cast<double> (count) * 1e9) / static_cast<double> (sum);
245 }
246
252 Duration percentile (double p) const noexcept
253 {
254 const uint64_t total = _count.load (std::memory_order_acquire);
255 if (total == 0)
256 {
257 return Duration (0);
258 }
259
260 const uint64_t target = static_cast<uint64_t> (p / 100.0 * static_cast<double> (total));
261 uint64_t cumulative = 0;
262
263 for (int i = 0; i < _countsLen; ++i)
264 {
265 cumulative += _counts[i].load (std::memory_order_relaxed);
266
267 if (cumulative > target)
268 {
269 return Duration (static_cast<typename Duration::rep> (bucketUpperBound (i)));
270 }
271 }
272
273 return Duration (static_cast<typename Duration::rep> (_maxTrackableValue));
274 }
275
276#ifdef JOIN_HAS_NUMA
282 int mbind (int numa) const noexcept
283 {
284 return _countsMem.mbind (numa);
285 }
286#endif
287
292 int mlock () const noexcept
293 {
294 return _countsMem.mlock ();
295 }
296
297 private:
303 static int hdrBucketIndex (uint64_t v) noexcept
304 {
305 const int pow2ceiling = 64 - __builtin_clzll (v | static_cast<uint64_t> (_subBucketCount - 1));
306 return std::max (0, pow2ceiling - (_subBucketHalfCountMagnitude + 1));
307 }
308
314 static int countsIndex (uint64_t ns) noexcept
315 {
316 if (ns == 0)
317 {
318 ns = 1; // LCOV_EXCL_LINE
319 }
320
321 const int bi = hdrBucketIndex (ns);
322 const int si = static_cast<int> (ns >> bi);
323 const int idx = (bi + 1) * _subBucketHalfCount + si - _subBucketHalfCount;
324
325 if (JOIN_UNLIKELY (idx >= _buckets))
326 {
327 return _overflowIdx; // LCOV_EXCL_LINE
328 }
329
330 return idx;
331 }
332
338 static uint64_t bucketUpperBound (int idx) noexcept
339 {
340 if (idx >= _overflowIdx)
341 {
342 return _maxTrackableValue; // LCOV_EXCL_LINE
343 }
344
345 const int bi = std::max (0, idx / _subBucketHalfCount - 1);
346 const int si = idx - bi * _subBucketHalfCount;
347
348 return static_cast<uint64_t> (si + 1) << bi;
349 }
350
352 static constexpr int _subBucketHalfCountMagnitude = 7;
353
355 static constexpr int _subBucketCount = 1 << (_subBucketHalfCountMagnitude + 1);
356
358 static constexpr int _subBucketHalfCount = _subBucketCount >> 1;
359
361 static constexpr int _bucketCount = 30;
362
364 static constexpr int _buckets = (_bucketCount + 1) * _subBucketHalfCount;
365
367 static constexpr int _countsLen = _buckets + 1;
368
370 static constexpr int _overflowIdx = _buckets;
371
373 static constexpr uint64_t _maxTrackableValue = static_cast<uint64_t> (_subBucketCount) << (_bucketCount - 1);
374
376 static constexpr uint64_t _countsSize = static_cast<uint64_t> (_countsLen) * sizeof (std::atomic<uint64_t>);
377
379 LocalMem _countsMem;
380
382 std::atomic<uint64_t>* const _counts;
383
385 alignas (64) std::atomic_uint64_t _count{0};
386
388 alignas (64) std::atomic_uint64_t _last{0};
389
391 alignas (64) std::atomic_uint64_t _min{std::numeric_limits<uint64_t>::max ()};
392
394 alignas (64) std::atomic_uint64_t _max{0};
395
397 alignas (64) std::atomic_uint64_t _sum{0};
398
400 const std::string _name;
401
403 ClockPolicy _clock;
404 };
405
406 namespace details
407 {
409 constexpr int colMetric = 24;
410
412 constexpr int colCount = 14;
413
415 constexpr int colThroughput = 20;
416
418 constexpr int colLatency = 16;
419
422
427 inline int latencyScaleIndex () noexcept
428 {
429 static int idx = std::ios_base::xalloc ();
430 return idx;
431 }
432
437 inline int throughputScaleIndex () noexcept
438 {
439 static int idx = std::ios_base::xalloc ();
440 return idx;
441 }
442 }
443
449 inline std::ostream& statsHeader (std::ostream& out)
450 {
451 out << std::left << std::setw (details::colMetric) << "Metric" << std::right << std::setw (details::colCount)
452 << "Count" << std::setw (details::colThroughput) << "Throughput" << std::setw (details::colLatency) << "Min"
453 << std::setw (details::colLatency) << "Mean" << std::setw (details::colLatency) << "Max"
454 << std::setw (details::colLatency) << "P50" << std::setw (details::colLatency) << "P90"
455 << std::setw (details::colLatency) << "P99" << "\n"
456 << std::string (details::colTotal, '-');
457
458 return out;
459 }
460
466 inline std::ostream& nsec (std::ostream& out)
467 {
468 out.iword (details::latencyScaleIndex ()) = 1;
469 return out;
470 }
471
477 inline std::ostream& usec (std::ostream& out)
478 {
479 out.iword (details::latencyScaleIndex ()) = 1'000;
480 return out;
481 }
482
488 inline std::ostream& msec (std::ostream& out)
489 {
490 out.iword (details::latencyScaleIndex ()) = 1'000'000;
491 return out;
492 }
493
499 inline std::ostream& sec (std::ostream& out)
500 {
501 out.iword (details::latencyScaleIndex ()) = 1'000'000'000;
502 return out;
503 }
504
510 inline std::ostream& ops (std::ostream& out)
511 {
512 out.iword (details::throughputScaleIndex ()) = 1;
513 return out;
514 }
515
521 inline std::ostream& kops (std::ostream& out)
522 {
523 out.iword (details::throughputScaleIndex ()) = 1'000;
524 return out;
525 }
526
532 inline std::ostream& mops (std::ostream& out)
533 {
534 out.iword (details::throughputScaleIndex ()) = 1'000'000;
535 return out;
536 }
537
543 inline std::ostream& gops (std::ostream& out)
544 {
545 out.iword (details::throughputScaleIndex ()) = 1'000'000'000;
546 return out;
547 }
548
555 template <class ClockPolicy>
556 inline std::ostream& operator<< (std::ostream& out, const BasicStats<ClockPolicy>& statistics)
557 {
558 // latency scale.
559 const long lscale = [&] {
560 const long s = out.iword (details::latencyScaleIndex ());
561 return s == 0 ? 1L : s;
562 }();
563
564 const double dlscale = static_cast<double> (lscale);
565 const char* lunit = "ns";
566 if (lscale == 1'000'000'000)
567 {
568 lunit = "s";
569 }
570 else if (lscale == 1'000'000)
571 {
572 lunit = "ms";
573 }
574 else if (lscale == 1'000)
575 {
576 lunit = "us";
577 }
578
579 // throughput scale.
580 const long tscale = [&] {
581 const long s = out.iword (details::throughputScaleIndex ());
582 return s == 0 ? 1L : s;
583 }();
584
585 const double dtscale = static_cast<double> (tscale);
586 const char* tunit = "ops/s";
587 if (tscale == 1'000'000'000)
588 {
589 tunit = "Gops/s";
590 }
591 else if (tscale == 1'000'000)
592 {
593 tunit = "Mops/s";
594 }
595 else if (tscale == 1'000)
596 {
597 tunit = "Kops/s";
598 }
599
600 // format statistics.
601 const auto count = statistics.count ();
602 const auto min = statistics.min ();
603 const auto mean = statistics.mean ();
604 const auto max = statistics.max ();
605 const auto thr = statistics.throughput ();
606 const auto p50 = statistics.percentile (50.0);
607 const auto p90 = statistics.percentile (90.0);
608 const auto p99 = statistics.percentile (99.0);
609
610 auto printLatCol = [&] (double v) {
611 std::ostringstream ss;
612 ss << std::fixed << std::setprecision (out.precision ()) << v << " (" << lunit << ")";
613 out << std::setw (details::colLatency) << ss.str ();
614 };
615 std::ostringstream oss;
616 oss << std::fixed << std::setprecision (out.precision ()) << thr / dtscale << " (" << tunit << ")";
617 out << std::left << std::setw (details::colMetric) << statistics.name () << std::right
618 << std::setw (details::colCount) << count << std::setw (details::colThroughput) << oss.str ();
619 printLatCol (static_cast<double> (min.count ()) / dlscale);
620 printLatCol (mean.count () / dlscale);
621 printLatCol (static_cast<double> (max.count ()) / dlscale);
622 printLatCol (static_cast<double> (p50.count ()) / dlscale);
623 printLatCol (static_cast<double> (p90.count ()) / dlscale);
624 printLatCol (static_cast<double> (p99.count ()) / dlscale);
625
626 return out;
627 }
628
632 template <typename Statistics>
634 {
635 public:
636 using TimePoint = typename Statistics::TimePoint;
637
642 explicit ScopedStats (Statistics& stats) noexcept
643 : _statistics (stats)
644 , _start (_statistics.start ())
645 {
646 }
647
652 ScopedStats (const ScopedStats& other) = delete;
653
659 ScopedStats& operator= (const ScopedStats& other) = delete;
660
665 ScopedStats (ScopedStats&& other) = delete;
666
673
677 ~ScopedStats () noexcept
678 {
679 _statistics.stop (_start);
680 }
681
682 private:
684 Statistics& _statistics;
685
687 TimePoint _start;
688 };
689}
690
691#endif
lock-free, multi-producer-safe performance statistics collector.
Definition statistics.hpp:50
~BasicStats() noexcept
destroy instance.
Definition statistics.hpp:99
BasicStats(BasicStats &&other)=delete
move constructor.
std::chrono::duration< double, std::nano > mean() const noexcept
arithmetic mean of all completed intervals.
Definition statistics.hpp:219
Duration last() const noexcept
duration of the most recently completed interval.
Definition statistics.hpp:180
int mlock() const noexcept
lock histogram memory in RAM.
Definition statistics.hpp:292
typename ClockPolicy::TimePoint TimePoint
Definition statistics.hpp:53
TimePoint start() const noexcept
mark the beginning of a measured interval.
Definition statistics.hpp:120
void stop(TimePoint startTime) noexcept
mark the end of a measured interval and update all aggregates.
Definition statistics.hpp:129
BasicStats(const std::string &name={})
create instance.
Definition statistics.hpp:59
typename ClockPolicy::Duration Duration
Definition statistics.hpp:52
const std::string & name() const noexcept
get metric name.
Definition statistics.hpp:111
BasicStats(const BasicStats &other)=delete
copy constructor.
Duration min() const noexcept
minimum duration observed across all completed intervals.
Definition statistics.hpp:193
double throughput() const noexcept
operations per second.
Definition statistics.hpp:234
void reset() noexcept
reset all accumulators to their initial state.
Definition statistics.hpp:154
BasicStats & operator=(const BasicStats &other)=delete
copy assignment.
Duration max() const noexcept
maximum duration observed across all completed intervals.
Definition statistics.hpp:206
Duration percentile(double p) const noexcept
compute the requested percentile from the HDR histogram.
Definition statistics.hpp:252
uint64_t count() const noexcept
total number of completed intervals.
Definition statistics.hpp:171
int mlock() const noexcept
lock memory in RAM.
Definition memory.hpp:256
RAII guard that automatically calls start() on construction and stop() on destruction.
Definition statistics.hpp:634
ScopedStats(ScopedStats &&other)=delete
move constructor.
~ScopedStats() noexcept
destructor.
Definition statistics.hpp:677
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:642
typename Statistics::TimePoint TimePoint
Definition statistics.hpp:636
constexpr int colCount
width of the sample count column.
Definition statistics.hpp:412
int throughputScaleIndex() noexcept
returns the xalloc index used to store the throughput scale on a stream.
Definition statistics.hpp:437
constexpr int colLatency
width of the latency columns.
Definition statistics.hpp:418
constexpr int colTotal
total table width.
Definition statistics.hpp:421
int latencyScaleIndex() noexcept
returns the xalloc index used to store the latency scale on a stream.
Definition statistics.hpp:427
constexpr int colThroughput
width of the throughput column.
Definition statistics.hpp:415
constexpr int colMetric
width of the metric name column.
Definition statistics.hpp:409
Definition acceptor.hpp:32
std::ostream & kops(std::ostream &out)
set throughput display unit to Kops/sec.
Definition statistics.hpp:521
std::ostream & msec(std::ostream &out)
set latency display unit to milliseconds.
Definition statistics.hpp:488
std::ostream & sec(std::ostream &out)
set latency display unit to seconds.
Definition statistics.hpp:499
std::ostream & operator<<(std::ostream &os, const BasicUnixEndpoint< Protocol > &endpoint)
push endpoint representation into a stream.
Definition endpoint.hpp:255
std::ostream & ops(std::ostream &out)
set throughput display unit to ops/sec.
Definition statistics.hpp:510
std::ostream & gops(std::ostream &out)
set throughput display unit to Gops/sec.
Definition statistics.hpp:543
std::ostream & nsec(std::ostream &out)
set latency display unit to nanoseconds.
Definition statistics.hpp:466
std::ostream & usec(std::ostream &out)
set latency display unit to microseconds.
Definition statistics.hpp:477
std::ostream & mops(std::ostream &out)
set throughput display unit to Mops/sec.
Definition statistics.hpp:532
std::ostream & statsHeader(std::ostream &out)
print the statistics table header to a stream.
Definition statistics.hpp:449
Definition error.hpp:137
#define JOIN_UNLIKELY(x)
Definition utils.hpp:47