]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add generic Histogram class. Counters can be atomic if needed.
authorOtto <otto.moerbeek@open-xchange.com>
Fri, 19 Feb 2021 09:24:03 +0000 (10:24 +0100)
committerOtto <otto.moerbeek@open-xchange.com>
Fri, 19 Feb 2021 09:26:40 +0000 (10:26 +0100)
pdns/histogram.hh [new file with mode: 0644]
pdns/recursordist/Makefile.am
pdns/recursordist/histogram.hh [new symlink]
pdns/recursordist/test-histogram_hh.cc [new file with mode: 0644]

diff --git a/pdns/histogram.hh b/pdns/histogram.hh
new file mode 100644 (file)
index 0000000..d5d3202
--- /dev/null
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <vector>
+#include <sstream>
+
+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<uint64_t> d_count;
+};
+
+inline bool operator<(uint64_t b, const AtomicBucket& bu)
+{
+  // we are using less-or-equal
+  return b <= bu.d_boundary;
+}
+
+template<class B>
+class BaseHistogram
+{
+public:
+  BaseHistogram(const std::string& prefix, const std::vector<uint64_t>& 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<uint64_t>::max(), 0});
+  }
+
+  const std::vector<B>& getRawData() const
+  {
+    return d_buckets;
+  }
+
+  uint64_t getCount(size_t i) const
+  {
+    return d_buckets[i].d_count;
+  }
+
+  std::vector<B> getCumulativeBuckets() const
+  {
+    std::vector<B> 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<uint64_t> getCumulativeCounts() const
+  {
+    std::vector<uint64_t> 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<B> d_buckets;
+};
+
+template<class T>
+using Histogram = BaseHistogram<Bucket>;
+
+template<class T>
+using AtomicHistogram = BaseHistogram<AtomicBucket>;
+
+} // namespace pdns
index 2bc4043565068fd3738fbae47ffb79792a3bacdd..2829db7ccd66aa701deb504cdf9aa04f7a127ef6 100644 (file)
@@ -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 (symlink)
index 0000000..94e4ad0
--- /dev/null
@@ -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 (file)
index 0000000..357c05a
--- /dev/null
@@ -0,0 +1,42 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+
+#include "histogram.hh"
+
+BOOST_AUTO_TEST_SUITE(histogram_hh)
+
+BOOST_AUTO_TEST_CASE(test_simple) {
+  auto h = pdns::AtomicHistogram<uint64_t>("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()