]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Fix dynamic block metrics collection
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 25 Nov 2020 16:19:09 +0000 (17:19 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 27 Nov 2020 10:00:39 +0000 (11:00 +0100)
pdns/dnsdist-dynblocks.hh
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdistdist/dnsdist-dynblocks.cc
pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc

index 5e23d9002547ac3f29fa3fe4e68f4008ea883bc1..8ebe2cfe85b89e9e208d1fce00f66c6efee0538a 100644 (file)
@@ -314,8 +314,6 @@ public:
     d_beQuiet = quiet;
   }
 
-  void purgeExpired(const struct timespec& now);
-
 private:
 
   bool checkIfQueryTypeMatches(const Rings::Query& query);
@@ -369,26 +367,34 @@ private:
   bool d_beQuiet{false};
 };
 
-class DynBlockRulesMetricsCache
+class DynBlockMaintenance
 {
 public:
-  DynBlockRulesMetricsCache(size_t topN, unsigned int validity): d_validityPeriod(validity), d_topN(topN)
-  {
-  }
+  static void run();
 
-  std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> getTopNetmasks();
-  std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes();
-  void invalidate();
-  void setParameters(size_t topN, unsigned int validity);
+  /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
+  static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> getHitsForTopNetmasks();
+  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
+
+  /* get the the top offenders based on the current value of the counters */
+  static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> getTopNetmasks();
+  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes();
+  static void purgeExpired(const struct timespec& now);
 
 private:
-  std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> d_cachedNetmasks;
-  std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> d_cachedSuffixes;
-  std::mutex d_mutex;
-  time_t d_netmasksValidUntil{0};
-  time_t d_suffixesValidUntil{0};
-  unsigned int d_validityPeriod{0};
-  size_t d_topN{0};
-};
+  static void collectMetrics();
+  static void generateMetrics();
 
-extern DynBlockRulesMetricsCache g_dynBlocksMetricsCache;
+  struct MetricsSnapshot
+  {
+    std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> nmgData;
+    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
+  };
+
+  static std::mutex s_topsMutex;
+  // need N+1 datapoints to be able to do the diff after a collection point has been reached
+  static std::list<MetricsSnapshot> s_metricsData;
+  static std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> s_topNMGsByReason;
+  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> s_topSMTsByReason;
+  static size_t s_topN;
+};
index dcd56b2201c2470188957bacee002af3df78a07f..4b5b63a5cd5b3764cc8faf8db71e0de32296f812 100644 (file)
@@ -668,21 +668,21 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
   addRulesToPrometheusOutput(output, g_cachehitresprulactions);
   addRulesToPrometheusOutput(output, g_selfansweredresprulactions);
 
-  output << "# HELP dnsdist_dynblocks_nmg_top_offenders " << "Top offenders blocked by Dynamic Blocks (netmasks)" << "\n";
-  output << "# TYPE dnsdist_dynblocks_nmg_top_offenders " << "gauge" << "\n";
-  auto topNetmasksByReason = g_dynBlocksMetricsCache.getTopNetmasks();
+  output << "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
+  output << "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
+  auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks();
   for (const auto& entry : topNetmasksByReason) {
     for (const auto& netmask : entry.second) {
-      output << "dnsdist_dynblocks_nmg_top_offenders{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
+      output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
     }
   }
 
-  output << "# HELP dnsdist_dynblocks_smt_top_offenders " << "Top offenders blocked by Dynamic Blocks (suffixes)" << "\n";
-  output << "# TYPE dnsdist_dynblocks_smt_top_offenders " << "gauge" << "\n";
-  auto topSuffixesByReason = g_dynBlocksMetricsCache.getTopSuffixes();
+  output << "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
+  output << "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
+  auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes();
   for (const auto& entry : topSuffixesByReason) {
     for (const auto& suffix : entry.second) {
-      output << "dnsdist_dynblocks_smt_top_offenders{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
+      output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
     }
   }
 
index cd7b0b93967b746ba6ce48d119699905b738031d..e414ddbd29d9a23db9014fe09dacd3bc5eb4bf5e 100644 (file)
@@ -47,6 +47,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-cache.hh"
 #include "dnsdist-console.hh"
+#include "dnsdist-dynblocks.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
@@ -1569,18 +1570,18 @@ static void maintThread()
   size_t counter = 0;
   int32_t secondsToWaitLog = 0;
 
-  for(;;) {
+  for (;;) {
     sleep(interval);
 
     {
       std::lock_guard<std::mutex> lock(g_luamutex);
       auto f = g_lua.readVariable<boost::optional<std::function<void()> > >("maintenance");
-      if(f) {
+      if (f) {
         try {
           (*f)();
           secondsToWaitLog = 0;
         }
-        catch(std::exception &e) {
+        catch(const std::exception &e) {
           if (secondsToWaitLog <= 0) {
             infolog("Error during execution of maintenance function: %s", e.what());
             secondsToWaitLog = 61;
@@ -1631,11 +1632,16 @@ static void maintThread()
       }
       counter = 0;
     }
-
-    // ponder pruning g_dynblocks of expired entries here
   }
 }
 
+static void dynBlockMaintenanceThread()
+{
+  setThreadName("dnsdist/dynBloc");
+
+  DynBlockMaintenance::run();
+}
+
 static void secPollThread()
 {
   setThreadName("dnsdist/secpoll");
@@ -2412,6 +2418,9 @@ try
   
   thread healththread(healthChecksThread);
 
+  thread dynBlockMaintThread(dynBlockMaintenanceThread);
+  dynBlockMaintThread.detach();
+
   if (!g_secPollSuffix.empty()) {
     thread secpollthread(secPollThread);
     secpollthread.detach();
index 65ae642a249dc882691f8147b297dbc607b0392c..a24bd906152089b1ef177c4603bf80cbf42c8e61 100644 (file)
@@ -338,7 +338,7 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root,
   }
 }
 
-void DynBlockRulesGroup::purgeExpired(const struct timespec& now)
+void DynBlockMaintenance::purgeExpired(const struct timespec& now)
 {
   {
     auto blocks = g_dynblockNMG.getLocal();
@@ -375,94 +375,287 @@ void DynBlockRulesGroup::purgeExpired(const struct timespec& now)
   }
 }
 
-std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> DynBlockRulesMetricsCache::getTopNetmasks()
+std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> DynBlockMaintenance::getTopNetmasks()
 {
   std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> results;
-  if (d_topN == 0) {
+  if (s_topN == 0) {
     return results;
   }
 
-  time_t now = time(nullptr);
-  {
-    std::lock_guard<std::mutex> lock(d_mutex);
-    if (now < d_netmasksValidUntil) {
-      return d_cachedNetmasks;
-    }
-
-    auto blocks = g_dynblockNMG.getLocal();
-    for (const auto& entry : *blocks) {
-      auto& topsForReason = results[entry.second.reason];
-      if (topsForReason.size() < d_topN || topsForReason.front().second < entry.second.blocks) {
-        auto newEntry = std::make_pair(entry.first, entry.second.blocks.load());
+  auto blocks = g_dynblockNMG.getLocal();
+  for (const auto& entry : *blocks) {
+    auto& topsForReason = results[entry.second.reason];
+    if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.blocks) {
+      auto newEntry = std::make_pair(entry.first, entry.second.blocks.load());
 
-        if (topsForReason.size() >= d_topN) {
-          topsForReason.pop_front();
-        }
-
-        topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<Netmask, unsigned int>& a, const std::pair<Netmask, unsigned int>& b) {
-          return a.second < b.second;
-        }),
-          newEntry);
+      if (topsForReason.size() >= s_topN) {
+        topsForReason.pop_front();
       }
+
+      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<Netmask, unsigned int>& a, const std::pair<Netmask, unsigned int>& b) {
+        return a.second < b.second;
+      }),
+        newEntry);
     }
-    d_cachedNetmasks = results;
-    d_netmasksValidUntil = time(nullptr) + d_validityPeriod;
   }
 
   return results;
 }
 
-std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockRulesMetricsCache::getTopSuffixes()
+std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaintenance::getTopSuffixes()
 {
   std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> results;
-  if (d_topN == 0) {
+  if (s_topN == 0) {
     return results;
   }
 
-  time_t now = time(nullptr);
+  auto blocks = g_dynblockSMT.getLocal();
+  blocks->visit([&results](const SuffixMatchTree<DynBlock>& node) {
+    auto& topsForReason = results[node.d_value.reason];
+    if (topsForReason.size() < DynBlockMaintenance::s_topN || topsForReason.front().second < node.d_value.blocks) {
+      auto newEntry = std::make_pair(node.d_value.domain, node.d_value.blocks.load());
+
+      if (topsForReason.size() >= DynBlockMaintenance::s_topN) {
+        topsForReason.pop_front();
+      }
+
+      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
+        return a.second < b.second;
+      }),
+        newEntry);
+    }
+  });
+
+  return results;
+}
+
+struct DynBlockEntryStat
+{
+  size_t sum;
+  unsigned int lastSeenValue{0};
+};
+
+std::mutex DynBlockMaintenance::s_topsMutex;
+std::list<DynBlockMaintenance::MetricsSnapshot> DynBlockMaintenance::s_metricsData;
+std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> DynBlockMaintenance::s_topNMGsByReason;
+std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaintenance::s_topSMTsByReason;
+size_t DynBlockMaintenance::s_topN{20};
+
+void DynBlockMaintenance::collectMetrics()
+{
+  MetricsSnapshot snapshot;
+  snapshot.smtData = getTopSuffixes();
+  snapshot.nmgData = getTopNetmasks();
+
   {
-    std::lock_guard<std::mutex> lock(d_mutex);
-    if (now < d_suffixesValidUntil) {
-      return d_cachedSuffixes;
+    std::lock_guard<std::mutex> lock(s_topsMutex);
+    if (s_metricsData.size() >= 7) {
+      s_metricsData.pop_front();
     }
+    s_metricsData.push_back(std::move(snapshot));
+  }
+}
 
-    auto blocks = g_dynblockSMT.getLocal();
-    blocks->visit([&results, this](const SuffixMatchTree<DynBlock>& node) {
-      auto& topsForReason = results[node.d_value.reason];
-      if (topsForReason.size() < d_topN || topsForReason.front().second < node.d_value.blocks) {
-        auto newEntry = std::make_pair(node.d_value.domain, node.d_value.blocks.load());
+void DynBlockMaintenance::generateMetrics()
+{
+  if (s_metricsData.empty()) {
+    return;
+  }
+
+  /* do NMG */
+  std::map<std::string, std::map<Netmask, DynBlockEntryStat>> nm;
+  for (const auto& reason : s_metricsData.front().nmgData) {
+    auto& reasonStat = nm[reason.first];
+
+    /* prepare the counters by scanning the oldest entry (N+1) */
+    for (const auto& entry : reason.second) {
+      auto& stat = reasonStat[entry.first];
+      stat.sum = 0;
+      stat.lastSeenValue = entry.second;
+    }
+  }
 
-        if (topsForReason.size() >= d_topN) {
-          topsForReason.pop_front();
+  /* scan all the N entries, updating the counters */
+  bool first = true;
+  for (const auto& snap : s_metricsData) {
+    if (first) {
+      first = false;
+      continue;
+    }
+
+    auto& nmgData = snap.nmgData;
+    for (const auto& reason : nmgData) {
+      auto& reasonStat = nm[reason.first];
+      for (const auto& entry : reason.second) {
+        auto& stat = reasonStat[entry.first];
+        if (entry.second < stat.lastSeenValue) {
+          /* it wrapped, or we did not have a last value */
+          stat.sum += entry.second;
         }
+        else {
+          stat.sum += entry.second - stat.lastSeenValue;
+        }
+        stat.lastSeenValue = entry.second;
+      }
+    }
+  }
 
-        topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
+  /* now we need to get the top N entries (for each "reason") based on our counters (sum of the last N entries) */
+  std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> topNMGs;
+  {
+    for (const auto& reason : nm) {
+      auto& topsForReason = topNMGs[reason.first];
+      for (const auto& entry : reason.second) {
+        if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
+          /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
+          auto newEntry = std::pair<Netmask, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+          if (topsForReason.size() >= s_topN) {
+            topsForReason.pop_front();
+          }
+
+          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<Netmask, unsigned int>& a, const std::pair<Netmask, unsigned int>& b) {
             return a.second < b.second;
           }),
-          newEntry);
+            newEntry);
+        }
       }
-    });
-    d_cachedSuffixes = results;
-    d_suffixesValidUntil = time(nullptr) + d_validityPeriod;
+    }
   }
 
-  return results;
+  /* do SMT */
+  std::map<std::string, std::map<DNSName, DynBlockEntryStat>> smt;
+  for (const auto& reason : s_metricsData.front().smtData) {
+    auto& reasonStat = smt[reason.first];
+
+    /* prepare the counters by scanning the oldest entry (N+1) */
+    for (const auto& entry : reason.second) {
+      auto& stat = reasonStat[entry.first];
+      stat.sum = 0;
+      stat.lastSeenValue = entry.second;
+    }
+  }
+
+  /* scan all the N entries, updating the counters */
+  first = true;
+  for (const auto& snap : s_metricsData) {
+    if (first) {
+      first = false;
+      continue;
+    }
+
+    auto& smtData = snap.smtData;
+    for (const auto& reason : smtData) {
+      auto& reasonStat = smt[reason.first];
+      for (const auto& entry : reason.second) {
+        auto& stat = reasonStat[entry.first];
+        if (entry.second < stat.lastSeenValue) {
+          /* it wrapped, or we did not have a last value */
+          stat.sum = entry.second;
+        }
+        else {
+          stat.sum = entry.second - stat.lastSeenValue;
+        }
+        stat.lastSeenValue = entry.second;
+      }
+    }
+  }
+
+  /* now we need to get the top N entries (for each "reason") based on our counters (sum of the last N entries) */
+  std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTs;
+  {
+    for (const auto& reason : smt) {
+      auto& topsForReason = topSMTs[reason.first];
+      for (const auto& entry : reason.second) {
+        if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
+          /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
+          auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+          if (topsForReason.size() >= s_topN) {
+            topsForReason.pop_front();
+          }
+
+          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
+            return a.second < b.second;
+          }),
+            newEntry);
+        }
+      }
+    }
+  }
+
+  {
+    std::lock_guard<std::mutex> lock(s_topsMutex);
+    s_topNMGsByReason = std::move(topNMGs);
+    s_topSMTsByReason = std::move(topSMTs);
+  }
 }
 
-DynBlockRulesMetricsCache g_dynBlocksMetricsCache(20, 60);
+void DynBlockMaintenance::run()
+{
+  /* alright, so the main idea is to:
+     1/ clean up the NMG and SMT from expired entries from time to time
+     2/ generate metrics that can be used in the API and prometheus endpoints
+  */
+
+  static const time_t expiredPurgeInterval = 300;
+  static const time_t metricsCollectionInterval = 10;
+  static const time_t metricsGenerationInterval = 60;
+
+  time_t now = time(nullptr);
+  time_t nextExpiredPurge = now + expiredPurgeInterval;
+  time_t nextMetricsCollect = now + metricsCollectionInterval;
+  time_t nextMetricsGeneration = now + metricsGenerationInterval;
+
+  while (true) {
+    time_t sleepDelay = (nextExpiredPurge - now);
+    sleepDelay = std::min(sleepDelay, (nextMetricsCollect - now));
+    sleepDelay = std::min(sleepDelay, (nextMetricsGeneration - now));
+
+    sleep(sleepDelay);
+
+    try {
+      now = time(nullptr);
+      if (now >= nextMetricsCollect) {
+        /* every ten seconds we store the top N entries */
+        collectMetrics();
+
+        now = time(nullptr);
+        nextMetricsCollect = now + metricsCollectionInterval;
+      }
+
+      if (now >= nextMetricsGeneration) {
+        generateMetrics();
+
+        now = time(nullptr);
+        /* every minute we compute the averaged top N entries of the last 60 seconds,
+           and update the cached entry. */
+        nextMetricsGeneration = now + metricsGenerationInterval;
+      }
+
+      if (now >= nextExpiredPurge) {
+        struct timespec tspec;
+        gettime(&tspec);
+        purgeExpired(tspec);
+
+        now = time(nullptr);
+        nextExpiredPurge = now + expiredPurgeInterval;
+      }
+    }
+    catch (const std::exception& e) {
+      warnlog("Error in the dynamic block maintenance thread: %s", e.what());
+    }
+    catch (...) {
+    }
+  }
+}
 
-void DynBlockRulesMetricsCache::invalidate()
+std::map<std::string, std::list<std::pair<Netmask, unsigned int>>> DynBlockMaintenance::getHitsForTopNetmasks()
 {
-  std::lock_guard<std::mutex> lock(d_mutex);
-  d_netmasksValidUntil = 0;
-  d_suffixesValidUntil = 0;
-  d_cachedNetmasks.clear();
-  d_cachedSuffixes.clear();
+  std::lock_guard<std::mutex> lock(s_topsMutex);
+  return s_topNMGsByReason;
 }
 
-void DynBlockRulesMetricsCache::setParameters(size_t topN, unsigned int validity)
+std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaintenance::getHitsForTopSuffixes()
 {
-  d_validityPeriod = validity;
-  d_topN = topN;
-  invalidate();
+  std::lock_guard<std::mutex> lock(s_topsMutex);
+  return s_topSMTsByReason;
 }
index d950fac2c2129c1c217a8a002070d0742f945afd..3fbf322da65249bba322a40074a212d1093198f8 100644 (file)
@@ -714,8 +714,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
     /* now we ask for the top 20 offenders for each reason */
     StopWatch sw;
     sw.start();
-    DynBlockRulesMetricsCache cache(20, 1);
-    auto top = cache.getTopNetmasks();
+    auto top = DynBlockMaintenance::getTopNetmasks();
     BOOST_REQUIRE_EQUAL(top.size(), 1U);
     auto offenders = top.at(reason);
     BOOST_REQUIRE_EQUAL(offenders.size(), 20U);
@@ -728,7 +727,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
 
     struct timespec expired = now;
     expired.tv_sec += blockDuration + 1;
-    dbrg.purgeExpired(expired);
+    DynBlockMaintenance::purgeExpired(expired);
     BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
   }
 
@@ -771,8 +770,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
     /* now we ask for the top 20 offenders for each reason */
     StopWatch sw;
     sw.start();
-    DynBlockRulesMetricsCache cache(20, 1);
-    auto top = cache.getTopSuffixes();
+    auto top = DynBlockMaintenance::getTopSuffixes();
     BOOST_REQUIRE_EQUAL(top.size(), 1U);
     auto suffixes = top.at(reason);
     BOOST_REQUIRE_EQUAL(suffixes.size(), 20U);
@@ -785,10 +783,11 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
 
     struct timespec expired = now;
     expired.tv_sec += blockDuration + 1;
-    dbrg.purgeExpired(expired);
+    DynBlockMaintenance::purgeExpired(expired);
     BOOST_CHECK(g_dynblockSMT.getLocal()->getNodes().empty());
   }
 
+#define BENCH_DYNBLOCKS
 #ifdef BENCH_DYNBLOCKS
   {
     /* now insert 1M names */
@@ -827,14 +826,13 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
     cerr<<"added 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
 
     sw.start();
-    DynBlockRulesMetricsCache cache(20, 1);
-    auto top = cache.getTopSuffixes();
+    auto top = DynBlockMaintenance::getTopSuffixes();
     cerr<<"scanned 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
 
     struct timespec expired = now;
     expired.tv_sec += blockDuration + 1;
     sw.start();
-    dbrg.purgeExpired(expired);
+    DynBlockMaintenance::purgeExpired(expired);
     cerr<<"removed 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
   }
 #endif
@@ -871,14 +869,13 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
     BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1000000U);
 
     sw.start();
-    DynBlockRulesMetricsCache cache(20, 1);
-    auto top = cache.getTopNetmasks();
+    auto top = DynBlockMaintenance::getTopNetmasks();
     cerr<<"scanned "<<g_dynblockNMG.getLocal()->size()<<" entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
 
     struct timespec expired = now;
     expired.tv_sec += blockDuration + 1;
     sw.start();
-    dbrg.purgeExpired(expired);
+    DynBlockMaintenance::purgeExpired(expired);
     cerr<<"removed 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
     BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
   }