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
60 explicit BasicStats (const std::string& name = {})
61 : _countsMem (_countsSize)
62 , _counts (static_cast<std::atomic<uint64_t>*> (_countsMem.get ()))
63 , _name (name)
64 {
65 for (int i = 0; i < _countsLen; ++i)
66 {
67 new (&_counts[i]) std::atomic<uint64_t> (0);
68 }
69 }
70
75 BasicStats (const BasicStats& other) = delete;
76
82 BasicStats& operator= (const BasicStats& other) = delete;
83
88 BasicStats (BasicStats&& other) = delete;
89
95 BasicStats& operator= (BasicStats&& other) = delete;
96
100 ~BasicStats () noexcept
101 {
102 for (int i = 0; i < _countsLen; ++i)
103 {
104 _counts[i].~atomic<uint64_t> ();
105 }
106 }
107
112 const std::string& name () const noexcept
113 {
114 return _name;
115 }
116
121 TimePoint start () const noexcept
122 {
123 return ClockPolicy::now ();
124 }
125
130 void stop (TimePoint startTime) noexcept
131 {
132 const uint64_t ns =
133 static_cast<uint64_t> (std::chrono::duration_cast<Duration> (ClockPolicy::now () - startTime).count ());
134
135 _sum.fetch_add (ns, std::memory_order_relaxed);
136 _last.store (ns, std::memory_order_relaxed);
137
138 auto prev = _min.load (std::memory_order_relaxed);
139 while (ns < prev &&
140 !_min.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
141 ;
142
143 prev = _max.load (std::memory_order_relaxed);
144 while (ns > prev &&
145 !_max.compare_exchange_weak (prev, ns, std::memory_order_relaxed, std::memory_order_relaxed))
146 ;
147
148 _counts[countsIndex (ns)].fetch_add (1, std::memory_order_relaxed);
149 _count.fetch_add (1, std::memory_order_release);
150 }
151
155 void reset () noexcept
156 {
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)
163 {
164 _counts[i].store (0, std::memory_order_relaxed);
165 }
166 }
167
172 uint64_t count () const noexcept
173 {
174 return _count.load (std::memory_order_acquire);
175 }
176
181 Duration last () const noexcept
182 {
183 if (_count.load (std::memory_order_acquire) == 0)
184 {
185 return Duration (0);
186 }
187 return Duration (_last.load (std::memory_order_relaxed));
188 }
189
194 Duration min () const noexcept
195 {
196 if (_count.load (std::memory_order_acquire) == 0)
197 {
198 return Duration (0);
199 }
200 return Duration (_min.load (std::memory_order_relaxed));
201 }
202
207 Duration max () const noexcept
208 {
209 if (_count.load (std::memory_order_acquire) == 0)
210 {
211 return Duration (0);
212 }
213 return Duration (_max.load (std::memory_order_relaxed));
214 }
215
220 std::chrono::duration<double, std::nano> mean () const noexcept
221 {
222 const auto count = _count.load (std::memory_order_acquire);
223 if (count == 0)
224 {
225 return std::chrono::duration<double, std::nano> (0.0);
226 }
227 return std::chrono::duration<double, std::nano> (
228 static_cast<double> (_sum.load (std::memory_order_relaxed)) / static_cast<double> (count));
229 }
230
235 double throughput () const noexcept
236 {
237 const auto count = _count.load (std::memory_order_acquire);
238 const auto sum = _sum.load (std::memory_order_acquire);
239
240 if (count == 0 || sum == 0)
241 {
242 return 0.0;
243 }
244
245 return (static_cast<double> (count) * 1e9) / static_cast<double> (sum);
246 }
247
253 Duration percentile (double p) const noexcept
254 {
255 const uint64_t total = _count.load (std::memory_order_acquire);
256 if (total == 0)
257 {
258 return Duration (0);
259 }
260
261 const uint64_t target = static_cast<uint64_t> (p / 100.0 * static_cast<double> (total));
262 uint64_t cumulative = 0;
263
264 for (int i = 0; i < _countsLen; ++i)
265 {
266 cumulative += _counts[i].load (std::memory_order_relaxed);
267
268 if (cumulative > target)
269 {
270 return Duration (static_cast<typename Duration::rep> (bucketUpperBound (i)));
271 }
272 }
273
274 return Duration (static_cast<typename Duration::rep> (_maxTrackableValue));
275 }
276
277#ifdef JOIN_HAS_NUMA
283 int mbind (int numa) const noexcept
284 {
285 return _countsMem.mbind (numa);
286 }
287#endif
288
293 int mlock () const noexcept
294 {
295 return _countsMem.mlock ();
296 }
297
298 private:
304 static int hdrBucketIndex (uint64_t v) noexcept
305 {
306 const int pow2ceiling = 64 - __builtin_clzll (v | static_cast<uint64_t> (_subBucketCount - 1));
307 return std::max (0, pow2ceiling - (_subBucketHalfCountMagnitude + 1));
308 }
309
315 static int countsIndex (uint64_t ns) noexcept
316 {
317 if (ns == 0)
318 {
319 ns = 1; // LCOV_EXCL_LINE
320 }
321
322 const int bi = hdrBucketIndex (ns);
323 const int si = static_cast<int> (ns >> bi);
324 const int idx = (bi + 1) * _subBucketHalfCount + si - _subBucketHalfCount;
325
326 if (JOIN_UNLIKELY (idx >= _buckets))
327 {
328 return _overflowIdx; // LCOV_EXCL_LINE
329 }
330
331 return idx;
332 }
333
339 static uint64_t bucketUpperBound (int idx) noexcept
340 {
341 if (idx >= _overflowIdx)
342 {
343 return _maxTrackableValue; // LCOV_EXCL_LINE
344 }
345
346 const int bi = std::max (0, idx / _subBucketHalfCount - 1);
347 const int si = idx - bi * _subBucketHalfCount;
348
349 return static_cast<uint64_t> (si + 1) << bi;
350 }
351
353 static constexpr int _subBucketHalfCountMagnitude = 7;
354
356 static constexpr int _subBucketCount = 1 << (_subBucketHalfCountMagnitude + 1);
357
359 static constexpr int _subBucketHalfCount = _subBucketCount >> 1;
360
362 static constexpr int _bucketCount = 30;
363
365 static constexpr int _buckets = (_bucketCount + 1) * _subBucketHalfCount;
366
368 static constexpr int _countsLen = _buckets + 1;
369
371 static constexpr int _overflowIdx = _buckets;
372
374 static constexpr uint64_t _maxTrackableValue = static_cast<uint64_t> (_subBucketCount) << (_bucketCount - 1);
375
377 static constexpr uint64_t _countsSize = static_cast<uint64_t> (_countsLen) * sizeof (std::atomic<uint64_t>);
378
380 LocalMem _countsMem;
381
383 std::atomic<uint64_t>* const _counts;
384
386 alignas (64) std::atomic_uint64_t _count{0};
387
389 alignas (64) std::atomic_uint64_t _last{0};
390
392 alignas (64) std::atomic_uint64_t _min{std::numeric_limits<uint64_t>::max ()};
393
395 alignas (64) std::atomic_uint64_t _max{0};
396
398 alignas (64) std::atomic_uint64_t _sum{0};
399
401 const std::string _name;
402
404 ClockPolicy _clock;
405 };
406
407 namespace details
408 {
410 constexpr int colMetric = 24;
411
413 constexpr int colCount = 14;
414
416 constexpr int colThroughput = 20;
417
419 constexpr int colLatency = 16;
420
423
428 inline int latencyScaleIndex () noexcept
429 {
430 static int idx = std::ios_base::xalloc ();
431 return idx;
432 }
433
438 inline int throughputScaleIndex () noexcept
439 {
440 static int idx = std::ios_base::xalloc ();
441 return idx;
442 }
443 }
444
450 inline std::ostream& statsHeader (std::ostream& out)
451 {
452 out << std::left << std::setw (details::colMetric) << "Metric" << std::right << std::setw (details::colCount)
453 << "Count" << std::setw (details::colThroughput) << "Throughput" << std::setw (details::colLatency) << "Min"
454 << std::setw (details::colLatency) << "Mean" << std::setw (details::colLatency) << "Max"
455 << std::setw (details::colLatency) << "P50" << std::setw (details::colLatency) << "P90"
456 << std::setw (details::colLatency) << "P99" << "\n"
457 << std::string (details::colTotal, '-');
458
459 return out;
460 }
461
467 inline std::ostream& nsec (std::ostream& out)
468 {
469 out.iword (details::latencyScaleIndex ()) = 1;
470 return out;
471 }
472
478 inline std::ostream& usec (std::ostream& out)
479 {
480 out.iword (details::latencyScaleIndex ()) = 1'000;
481 return out;
482 }
483
489 inline std::ostream& msec (std::ostream& out)
490 {
491 out.iword (details::latencyScaleIndex ()) = 1'000'000;
492 return out;
493 }
494
500 inline std::ostream& sec (std::ostream& out)
501 {
502 out.iword (details::latencyScaleIndex ()) = 1'000'000'000;
503 return out;
504 }
505
511 inline std::ostream& ops (std::ostream& out)
512 {
513 out.iword (details::throughputScaleIndex ()) = 1;
514 return out;
515 }
516
522 inline std::ostream& kops (std::ostream& out)
523 {
524 out.iword (details::throughputScaleIndex ()) = 1'000;
525 return out;
526 }
527
533 inline std::ostream& mops (std::ostream& out)
534 {
535 out.iword (details::throughputScaleIndex ()) = 1'000'000;
536 return out;
537 }
538
544 inline std::ostream& gops (std::ostream& out)
545 {
546 out.iword (details::throughputScaleIndex ()) = 1'000'000'000;
547 return out;
548 }
549
556 template <class ClockPolicy>
557 inline std::ostream& operator<< (std::ostream& out, const BasicStats<ClockPolicy>& statistics)
558 {
559 // latency scale.
560 const long lscale = [&] {
561 const long s = out.iword (details::latencyScaleIndex ());
562 return s == 0 ? 1L : s;
563 }();
564
565 const double dlscale = static_cast<double> (lscale);
566 const char* lunit = "ns";
567 if (lscale == 1'000'000'000)
568 {
569 lunit = "s";
570 }
571 else if (lscale == 1'000'000)
572 {
573 lunit = "ms";
574 }
575 else if (lscale == 1'000)
576 {
577 lunit = "us";
578 }
579
580 // throughput scale.
581 const long tscale = [&] {
582 const long s = out.iword (details::throughputScaleIndex ());
583 return s == 0 ? 1L : s;
584 }();
585
586 const double dtscale = static_cast<double> (tscale);
587 const char* tunit = "ops/s";
588 if (tscale == 1'000'000'000)
589 {
590 tunit = "Gops/s";
591 }
592 else if (tscale == 1'000'000)
593 {
594 tunit = "Mops/s";
595 }
596 else if (tscale == 1'000)
597 {
598 tunit = "Kops/s";
599 }
600
601 // format statistics.
602 const auto count = statistics.count ();
603 const auto min = statistics.min ();
604 const auto mean = statistics.mean ();
605 const auto max = statistics.max ();
606 const auto thr = statistics.throughput ();
607 const auto p50 = statistics.percentile (50.0);
608 const auto p90 = statistics.percentile (90.0);
609 const auto p99 = statistics.percentile (99.0);
610
611 auto printLatCol = [&] (double v) {
612 std::ostringstream ss;
613 ss << std::fixed << std::setprecision (out.precision ()) << v << " (" << lunit << ")";
614 out << std::setw (details::colLatency) << ss.str ();
615 };
616 std::ostringstream oss;
617 oss << std::fixed << std::setprecision (out.precision ()) << thr / dtscale << " (" << tunit << ")";
618 out << std::left << std::setw (details::colMetric) << statistics.name () << std::right
619 << std::setw (details::colCount) << count << std::setw (details::colThroughput) << oss.str ();
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);
626
627 return out;
628 }
629
633 template <typename Statistics>
635 {
636 public:
637 using TimePoint = typename Statistics::TimePoint;
638
643 explicit ScopedStats (Statistics& stats) noexcept
644 : _statistics (stats)
645 , _start (_statistics.start ())
646 {
647 }
648
653 ScopedStats (const ScopedStats& other) = delete;
654
660 ScopedStats& operator= (const ScopedStats& other) = delete;
661
666 ScopedStats (ScopedStats&& other) = delete;
667
674
678 ~ScopedStats () noexcept
679 {
680 _statistics.stop (_start);
681 }
682
683 private:
685 Statistics& _statistics;
686
688 TimePoint _start;
689 };
690}
691
692#endif
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
Definition error.hpp:144
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51