From: Otto Date: Fri, 19 Feb 2021 09:24:03 +0000 (+0100) Subject: Add generic Histogram class. Counters can be atomic if needed. X-Git-Tag: dnsdist-1.6.0-alpha2~18^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2bd76c775a24da4a1826a39a02fb5c2685510cb3;p=thirdparty%2Fpdns.git Add generic Histogram class. Counters can be atomic if needed. --- diff --git a/pdns/histogram.hh b/pdns/histogram.hh new file mode 100644 index 0000000000..d5d3202295 --- /dev/null +++ b/pdns/histogram.hh @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +namespace pdns { + +// By convention, we are using microsecond units +struct Bucket +{ + std::string d_name; + uint64_t d_boundary; + uint64_t d_count; +}; + +inline bool operator<(uint64_t b, const Bucket& bu) +{ + // we are using less-or-equal + return b <= bu.d_boundary; +} + +struct AtomicBucket +{ + // We need the constrcutors in this case, since atomics have a disabled + // copy constructor. + AtomicBucket() {} + AtomicBucket(std::string name, uint64_t boundary, uint64_t val) : d_name(std::move(name)), d_boundary(boundary), d_count(val) {} + AtomicBucket(const AtomicBucket& rhs) : d_name(rhs.d_name), d_boundary(rhs.d_boundary), d_count(rhs.d_count.load()) {} + + std::string d_name; + uint64_t d_boundary; + std::atomic d_count; +}; + +inline bool operator<(uint64_t b, const AtomicBucket& bu) +{ + // we are using less-or-equal + return b <= bu.d_boundary; +} + +template +class BaseHistogram +{ +public: + BaseHistogram(const std::string& prefix, const std::vector& boundaries) + { + if (!std::is_sorted(boundaries.cbegin(), boundaries.cend())) { + throw std::invalid_argument("boundary array must be sorted"); + } + if (boundaries.size() == 0) { + throw std::invalid_argument("boundary array must not be empty"); + } + if (boundaries[0] == 0) { + throw std::invalid_argument("boundary array's first element should not be zero"); + } + d_buckets.reserve(boundaries.size() + 1); + for (auto b: boundaries) { + // to_string gives too many .00000's + std::ostringstream str; + str << prefix << "le-" << b; + d_buckets.push_back(B{str.str(), b, 0}); + } + // everything above last boundary, plus NaN, Inf etc + d_buckets.push_back(B{prefix + "le-max", std::numeric_limits::max(), 0}); + } + + const std::vector& getRawData() const + { + return d_buckets; + } + + uint64_t getCount(size_t i) const + { + return d_buckets[i].d_count; + } + + std::vector getCumulativeBuckets() const + { + std::vector ret; + ret.reserve(d_buckets.size()); + uint64_t c{0}; + for (const auto& b : d_buckets) { + c += b.d_count; + ret.push_back(B{b.d_name, b.d_boundary, c}); + } + return ret; + } + + std::vector getCumulativeCounts() const + { + std::vector ret; + ret.reserve(d_buckets.size()); + uint64_t c = 0; + for (const auto& b : d_buckets) { + c += b.d_count; + ret.push_back(c); + } + return ret; + } + + inline void operator()(uint64_t d) + { + auto index = std::upper_bound(d_buckets.begin(), d_buckets.end(), d); + // out index is always valid + ++index->d_count; + } + +private: + std::vector d_buckets; +}; + +template +using Histogram = BaseHistogram; + +template +using AtomicHistogram = BaseHistogram; + +} // namespace pdns diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 2bc4043565..2829db7ccd 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -120,6 +120,7 @@ pdns_recursor_SOURCES = \ filterpo.cc filterpo.hh \ fstrm_logger.cc fstrm_logger.hh \ gettime.cc gettime.hh \ + histogram.hh \ iputils.hh iputils.cc \ ixfr.cc ixfr.hh \ json.cc json.hh \ @@ -284,6 +285,7 @@ testrunner_SOURCES = \ test-dnsrecords_cc.cc \ test-ednsoptions_cc.cc \ test-filterpo_cc.cc \ + test-histogram_hh.cc \ test-iputils_hh.cc \ test-ixfr_cc.cc \ test-luawrapper.cc \ diff --git a/pdns/recursordist/histogram.hh b/pdns/recursordist/histogram.hh new file mode 120000 index 0000000000..94e4ad0b6c --- /dev/null +++ b/pdns/recursordist/histogram.hh @@ -0,0 +1 @@ +../histogram.hh \ No newline at end of file diff --git a/pdns/recursordist/test-histogram_hh.cc b/pdns/recursordist/test-histogram_hh.cc new file mode 100644 index 0000000000..357c05a9f0 --- /dev/null +++ b/pdns/recursordist/test-histogram_hh.cc @@ -0,0 +1,42 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include + +#include "histogram.hh" + +BOOST_AUTO_TEST_SUITE(histogram_hh) + +BOOST_AUTO_TEST_CASE(test_simple) { + auto h = pdns::AtomicHistogram("myname-", {1, 3, 5, 10, 100}); + + h(0); + h(1); + h(1); + h(3); + h(4); + h(100); + h(101); + h(-1); + + auto data = h.getRawData(); + BOOST_CHECK_EQUAL(data.size(), 6U); + uint64_t expected[] = { 3, 1, 1, 0, 1, 2}; + size_t i = 0; + for (auto e : expected) { + BOOST_CHECK_EQUAL(data[i++].d_count, e); + } + + auto c = h.getCumulativeCounts(); + BOOST_CHECK_EQUAL(data.size(), 6U); + uint64_t cexpected[] = { 3, 4, 5, 5, 6, 8}; + i = 0; + for (auto e : cexpected) { + BOOST_CHECK_EQUAL(c[i++], e); + } +} + +BOOST_AUTO_TEST_SUITE_END()