]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Use boost::mult-index for nsspeed table and make it shared.
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 28 Mar 2022 11:29:25 +0000 (13:29 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 30 Mar 2022 11:39:04 +0000 (13:39 +0200)
pdns/rec_channel_rec.cc
pdns/recursordist/rec-main.cc
pdns/syncres.cc
pdns/syncres.hh

index 9ea9589dbcfe294af0ceed9966b970d4977a5ffe..ea1d454990c52c05246e183b09273785b6dbc79e 100644 (file)
@@ -973,16 +973,6 @@ static uint64_t getNegCacheSize()
   return g_negCache->size();
 }
 
-uint64_t* pleaseGetNsSpeedsSize()
-{
-  return new uint64_t(SyncRes::getNSSpeedsSize());
-}
-
-static uint64_t getNsSpeedsSize()
-{
-  return broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize);
-}
-
 uint64_t* pleaseGetEDNSStatusesSize()
 {
   return new uint64_t(SyncRes::getEDNSStatusesSize());
@@ -1244,7 +1234,7 @@ static void registerAllStats1()
   addGetStat("negcache-entries", getNegCacheSize);
   addGetStat("throttle-entries", getThrottleSize);
 
-  addGetStat("nsspeeds-entries", getNsSpeedsSize);
+  addGetStat("nsspeeds-entries", SyncRes::getNSSpeedsSize);
   addGetStat("failed-host-entries", SyncRes::getFailedServersSize);
   addGetStat("non-resolving-nameserver-entries", SyncRes::getNonResolvingNSSize);
 
@@ -1988,7 +1978,7 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
     return doDumpToFile(s, pleaseDumpEDNSMap, cmd);
   }
   if (cmd == "dump-nsspeeds") {
-    return doDumpToFile(s, pleaseDumpNSSpeeds, cmd);
+    return doDumpToFile(s, pleaseDumpNSSpeeds, cmd, false);
   }
   if (cmd == "dump-failedservers") {
     return doDumpToFile(s, pleaseDumpFailedServers, cmd, false);
index be4ded3286174973ff2af4cf51dfadd932dfcf88..06c184be1de445391bf92dbb3d213a5c38726178 100644 (file)
@@ -891,7 +891,7 @@ static void doStats(void)
 
     g_log << Logger::Notice << "stats: throttle map: "
           << broadcastAccFunction<uint64_t>(pleaseGetThrottleSize) << ", ns speeds: "
-          << broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize) << ", failed ns: "
+          << SyncRes::getNSSpeedsSize() << ", failed ns: "
           << SyncRes::getFailedServersSize() << ", ednsmap: "
           << broadcastAccFunction<uint64_t>(pleaseGetEDNSStatusesSize) << ", non-resolving: "
           << SyncRes::getNonResolvingNSSize() << ", saved-parentsets: "
@@ -1876,12 +1876,6 @@ static void houseKeeping(void*)
       t_packetCache->doPruneTo(g_maxPacketCacheEntries / (RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers()));
     });
 
-    // This is a full scan
-    static thread_local PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 100};
-    pruneNSpeedTask.runIfDue(now, [now]() {
-      SyncRes::pruneNSSpeeds(now.tv_sec - 300);
-    });
-
     static thread_local PeriodicTask pruneEDNSTask{"pruneEDNSTask", 5}; // period could likely be longer
     pruneEDNSTask.runIfDue(now, [now]() {
       SyncRes::pruneEDNSStatuses(now.tv_sec - 2 * 3600);
@@ -1933,6 +1927,11 @@ static void houseKeeping(void*)
         }
       });
 
+      static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
+      pruneNSpeedTask.runIfDue(now, [now]() {
+        SyncRes::pruneNSSpeeds(now.tv_sec - 300);
+      });
+
       static PeriodicTask pruneFailedServersTask{"pruneFailedServerTask", 5};
       pruneFailedServersTask.runIfDue(now, [now]() {
         SyncRes::pruneFailedServers(now.tv_sec - SyncRes::s_serverdownthrottletime * 10);
index 3f80f1caea5287072020f83349421aa90e77879c..cc8eb80fefcefc6f35cc54471b7e253d1c9c8996 100644 (file)
@@ -107,6 +107,130 @@ private:
   cont_t d_cont;
 };
 
+/** Class that implements a decaying EWMA.
+    This class keeps an exponentially weighted moving average which, additionally, decays over time.
+    The decaying is only done on get.
+*/
+
+//! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
+/** Modelled to work mostly like the underlying DecayingEwma */
+class DecayingEwmaCollection
+{
+private:
+  struct DecayingEwma
+  {
+  public:
+    void submit(int val, const struct timeval& last, const struct timeval& now)
+    {
+      if (d_val == 0) {
+        d_val = static_cast<float>(val);
+      }
+      else {
+        float diff = makeFloat(last - now);
+        float factor = expf(diff) / 2.0f; // might be '0.5', or 0.0001
+        d_val = (1.0f - factor) * static_cast<float>(val) + factor * d_val;
+      }
+    }
+
+    float get(float factor)
+    {
+      return d_val *= factor;
+    }
+
+    float peek(void) const
+    {
+      return d_val;
+    }
+
+    float d_val{0};
+  };
+
+public:
+  DecayingEwmaCollection(const DNSName& name, const struct timeval ts = {0, 0})
+    : d_name(name), d_lastget(ts)
+  {
+  }
+
+  void submit(const ComboAddress& remote, int usecs, const struct timeval& now) const
+  {
+    d_collection[remote].submit(usecs, d_lastget, now);
+  }
+
+  float getFactor(const struct timeval &now) const
+  {
+    float diff = makeFloat(d_lastget - now);
+    return expf(diff / 60.0f); // is 1.0 or less
+  }
+
+  bool stale(time_t limit) const
+  {
+    return limit > d_lastget.tv_sec;
+  }
+
+  void purge(const std::map<ComboAddress, float>& keep) const
+  {
+    for (auto iter = d_collection.begin(); iter != d_collection.end(); ) {
+      if (keep.find(iter->first) != keep.end()) {
+        ++iter;
+      }
+      else {
+        iter = d_collection.erase(iter);
+      }
+    }
+  }
+
+  // d_collection is the modifyable part of the record, we index on DNSName and timeval, and DNSName never changes
+  mutable std::map<ComboAddress, DecayingEwma> d_collection;
+  const DNSName d_name;
+  struct timeval d_lastget;
+};
+
+class nsspeeds_t :
+  public multi_index_container<DecayingEwmaCollection,
+                               indexed_by<
+                                 hashed_unique<tag<DNSName>, member<DecayingEwmaCollection, const DNSName, &DecayingEwmaCollection::d_name>>,
+                                 ordered_non_unique<tag<timeval>, member<DecayingEwmaCollection, timeval, &DecayingEwmaCollection::d_lastget>>
+                                 >>
+{
+public:
+  const auto& find(const DNSName& name, const struct timeval& now)
+  {
+    const auto it = insert(DecayingEwmaCollection{name, now}).first;
+    return *it;
+  }
+
+  const auto& find(const DNSName& name)
+  {
+    const auto it = insert(DecayingEwmaCollection{name}).first;
+    return *it;
+  }
+
+  float fastest(const DNSName& name, const struct timeval& now)
+  {
+    auto& ind = get<DNSName>();
+    auto it = insert(DecayingEwmaCollection{name, now}).first;
+    if (it->d_collection.empty()) {
+      return 0;
+    }
+    // This could happen if find(DNSName) entered an entry; it's used only by test code
+    if (it->d_lastget.tv_sec == 0 && it->d_lastget.tv_usec == 0) {
+      ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+    }
+
+    float ret = std::numeric_limits<float>::max();
+    const float factor = it->getFactor(now);
+    for (auto& entry : it->d_collection) {
+      if (float tmp = entry.second.get(factor); tmp < ret) {
+        ret = tmp;
+      }
+    }
+    ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+    return ret;
+  }
+};
+
+static LockGuarded <nsspeeds_t> s_nsSpeeds;
+
 struct SavedParentEntry
 {
   SavedParentEntry(const DNSName& name, map<DNSName, vector<ComboAddress>>&& nsAddresses, time_t ttd)
@@ -713,7 +837,6 @@ bool SyncRes::isForwardOrAuth(const DNSName &qname) const {
   return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || !iter->second.shouldRecurse());
 }
 
-# if 0
 // Will be needed in the future
 static const char* timestamp(const struct timeval& tv, char* buf, size_t sz)
 {
@@ -736,7 +859,6 @@ static const char* timestamp(const struct timeval& tv, char* buf, size_t sz)
   }
   return buf;
 }
-#endif
 
 static const char* timestamp(time_t t, char* buf, size_t sz)
 {
@@ -776,6 +898,35 @@ uint64_t SyncRes::doEDNSDump(int fd)
   return count;
 }
 
+void SyncRes::pruneNSSpeeds(time_t limit)
+{
+  auto lock = s_nsSpeeds.lock();
+  auto &ind = lock->get<timeval>();
+  ind.erase(ind.begin(), ind.upper_bound(timeval{limit, 0}));
+}
+
+uint64_t SyncRes::getNSSpeedsSize()
+{
+  return s_nsSpeeds.lock()->size();
+}
+
+void SyncRes::submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now)
+{
+  auto lock = s_nsSpeeds.lock();
+  lock->find(server, now).submit(ca, usec, now);
+}
+
+void SyncRes::clearNSSpeeds()
+{
+  s_nsSpeeds.lock()->clear();
+}
+
+float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& ca)
+{
+  auto lock = s_nsSpeeds.lock();
+  return lock->find(server).d_collection[ca].peek();
+}
+
 uint64_t SyncRes::doDumpNSSpeeds(int fd)
 {
   int newfd = dup(fd);
@@ -787,19 +938,20 @@ uint64_t SyncRes::doDumpNSSpeeds(int fd)
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; nsspeed dump from thread follows\n;\n");
-  uint64_t count=0;
 
-  for(const auto& i : t_sstorage.nsSpeeds)
-  {
+  fprintf(fp.get(), "; nsspeed dump follows\n;\n");
+  uint64_t count = 0;
+
+  // Create a copy to avoid holding the lock while doing I/O
+  for (const auto& i : *s_nsSpeeds.lock()) {
     count++;
 
     // an <empty> can appear hear in case of authoritative (hosted) zones
-    fprintf(fp.get(), "%s -> ", i.first.toLogString().c_str());
-    for(const auto& j : i.second.d_collection)
-    {
+    char tmp[26];
+    fprintf(fp.get(), "%s\t%s\t", i.d_name.toLogString().c_str(), timestamp(i.d_lastget, tmp, sizeof(tmp)));
+    for (const auto& j : i.d_collection) {
       // typedef vector<pair<ComboAddress, DecayingEwma> > collection_t;
-      fprintf(fp.get(), "%s/%f ", j.first.toString().c_str(), j.second.peek());
+      fprintf(fp.get(), "%s/%f\t", j.first.toString().c_str(), j.second.peek());
     }
     fprintf(fp.get(), "\n");
   }
@@ -1646,14 +1798,16 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
      is only one or none at all in the current set.
   */
   map<ComboAddress, float> speeds;
-  auto& collection = t_sstorage.nsSpeeds[qname];
-  float factor = collection.getFactor(d_now);
-  for(const auto& val: ret) {
-    speeds[val] = collection.d_collection[val].get(factor);
+  {
+    auto lock = s_nsSpeeds.lock();
+    auto& collection = lock->find(qname, d_now);
+    float factor = collection.getFactor(d_now);
+    for(const auto& val: ret) {
+      speeds[val] = collection.d_collection[val].get(factor);
+    }
+    collection.purge(speeds);
   }
 
-  t_sstorage.nsSpeeds[qname].purge(speeds);
-
   if (ret.size() > 1) {
     shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
     speedOrderCA so(speeds);
@@ -2452,7 +2606,7 @@ inline std::vector<std::pair<DNSName, float>> SyncRes::shuffleInSpeedOrder(NsSet
   std::vector<std::pair<DNSName, float>> rnameservers;
   rnameservers.reserve(tnameservers.size());
   for(const auto& tns: tnameservers) {
-    float speed = t_sstorage.nsSpeeds[tns.first].get(d_now);
+    float speed = s_nsSpeeds.lock()->fastest(tns.first, d_now);
     rnameservers.emplace_back(tns.first, speed);
     if(tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
       return rnameservers;
@@ -2484,10 +2638,9 @@ inline vector<ComboAddress> SyncRes::shuffleForwardSpeed(const vector<ComboAddre
   map<ComboAddress, float> speeds;
 
   for(const auto& val: nameservers) {
-    float speed;
     DNSName nsName = DNSName(val.toStringWithPort());
-    speed=t_sstorage.nsSpeeds[nsName].get(d_now);
-    speeds[val]=speed;
+    float speed = s_nsSpeeds.lock()->fastest(nsName, d_now);
+    speeds[val] = speed;
   }
   shuffle(nameservers.begin(),nameservers.end(), pdns::dns_random_engine());
   speedOrderCA so(speeds);
@@ -4563,7 +4716,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     if (resolveret != LWResult::Result::OSLimitError && !chained && !dontThrottle) {
       // don't account for resource limits, they are our own fault
       // And don't throttle when the IP address is on the dontThrottleNetmasks list or the name is part of dontThrottleNames
-      t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, d_now); // 1 sec
+      s_nsSpeeds.lock()->find(nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
 
       // code below makes sure we don't filter COM or the root
       if (s_serverdownmaxfails > 0 && (auth != g_rootdnsname) && s_fails.lock()->incr(remoteIP, d_now) >= s_serverdownmaxfails) {
@@ -4589,7 +4742,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     if (!chained && !dontThrottle) {
 
       // let's make sure we prefer a different server for some time, if there is one available
-      t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, d_now); // 1 sec
+      s_nsSpeeds.lock()->find(nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
 
       if (doTCP) {
         // we can be more heavy-handed over TCP
@@ -4610,7 +4763,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
           // rather than throttling what could be the only server we have for this destination, let's make sure we try a different one if there is one available
           // on the other hand, we might keep hammering a server under attack if there is no other alternative, or the alternative is overwhelmed as well, but
           // at the very least we will detect that if our packets stop being answered
-          t_sstorage.nsSpeeds[nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName].submit(remoteIP, 1000000, d_now); // 1 sec
+          s_nsSpeeds.lock()->find(nsName.empty()? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
         }
         else {
           t_sstorage.throttle.throttle(d_now.tv_sec, std::make_tuple(remoteIP, qname, qtype.getCode()), 60, 3);
@@ -5019,7 +5172,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           */
           //        cout<<"msec: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
 
-          t_sstorage.nsSpeeds[tns->first.empty()? DNSName(remoteIP->toStringWithPort()) : tns->first].submit(*remoteIP, lwr.d_usec, d_now);
+          s_nsSpeeds.lock()->find(tns->first.empty()? DNSName(remoteIP->toStringWithPort()) : tns->first, d_now).submit(*remoteIP, lwr.d_usec, d_now);
 
           /* we have received an answer, are we done ? */
           bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state, *remoteIP);
index c9e3abc0f1d446a17ae685a3c46189060c986a1a..e6e25ebd64afb7f3da10cc55e89963b1d0aafc9f 100644 (file)
@@ -151,47 +151,6 @@ private:
   cont_t d_cont;
 };
 
-
-/** Class that implements a decaying EWMA.
-    This class keeps an exponentially weighted moving average which, additionally, decays over time.
-    The decaying is only done on get.
-*/
-class DecayingEwma
-{
-public:
-  DecayingEwma() {}
-  DecayingEwma(const DecayingEwma& orig) = delete;
-  DecayingEwma & operator=(const DecayingEwma& orig) = delete;
-  
-  void submit(int val, const struct timeval& now)
-  {
-    if (d_last.tv_sec == 0 && d_last.tv_usec == 0) {
-      d_last = now;
-      d_val = val;
-    }
-    else {
-      float diff = makeFloat(d_last - now);
-      d_last = now;
-      float factor = expf(diff)/2.0f; // might be '0.5', or 0.0001
-      d_val = (1-factor)*val + factor*d_val;
-    }
-  }
-
-  float get(float factor)
-  {
-    return d_val *= factor;
-  }
-
-  float peek(void) const
-  {
-    return d_val;
-  }
-
-private:
-  struct timeval d_last{0, 0};          // stores time
-  float d_val{0};
-};
-
 extern std::unique_ptr<NegCache> g_negCache;
 
 class SyncRes : public boost::noncopyable
@@ -202,65 +161,6 @@ public:
 
   enum class HardenNXD { No, DNSSEC, Yes };
 
-  //! This represents a number of decaying Ewmas, used to store performance per nameserver-name.
-  /** Modelled to work mostly like the underlying DecayingEwma */
-  struct DecayingEwmaCollection
-  {
-    void submit(const ComboAddress& remote, int usecs, const struct timeval& now)
-    {
-      d_collection[remote].submit(usecs, now);
-    }
-
-    float getFactor(const struct timeval &now) {
-      float diff = makeFloat(d_lastget - now);
-      return expf(diff / 60.0f); // is 1.0 or less
-    }
-    
-    float get(const struct timeval& now)
-    {
-      if (d_collection.empty()) {
-        return 0;
-      }
-      if (d_lastget.tv_sec == 0 && d_lastget.tv_usec == 0) {
-        d_lastget = now;
-      }
-
-      float ret = std::numeric_limits<float>::max();
-      float factor = getFactor(now);
-      float tmp;
-      for (auto& entry : d_collection) {
-        if ((tmp = entry.second.get(factor)) < ret) {
-          ret = tmp;
-        }
-      }
-      d_lastget = now;
-      return ret;
-    }
-
-    bool stale(time_t limit) const
-    {
-      return limit > d_lastget.tv_sec;
-    }
-
-    void purge(const std::map<ComboAddress, float>& keep)
-    {
-      for (auto iter = d_collection.begin(); iter != d_collection.end(); ) {
-        if (keep.find(iter->first) != keep.end()) {
-          ++iter;
-        }
-        else {
-          iter = d_collection.erase(iter);
-        }
-      }
-    }
-
-    typedef std::map<ComboAddress, DecayingEwma> collection_t;
-    collection_t d_collection;
-    struct timeval d_lastget{0, 0};       // stores time
-  };
-
-  typedef std::unordered_map<DNSName, DecayingEwmaCollection> nsspeeds_t;
-
   vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, bool bogusOnNXD=true, bool* foundCut=nullptr);
 
   class AuthDomain
@@ -339,7 +239,6 @@ public:
   };
 
   struct ThreadLocalStorage {
-    nsspeeds_t nsSpeeds;
     throttle_t throttle;
     ednsstatus_t ednsstatus;
     std::shared_ptr<domainmap_t> domainmap;
@@ -401,33 +300,13 @@ public:
   {
     s_ednsdomains = SuffixMatchNode();
   }
-  static void pruneNSSpeeds(time_t limit)
-  {
-    for(auto i = t_sstorage.nsSpeeds.begin(), end = t_sstorage.nsSpeeds.end(); i != end; ) {
-      if(i->second.stale(limit)) {
-        i = t_sstorage.nsSpeeds.erase(i);
-      }
-      else {
-        ++i;
-      }
-    }
-  }
-  static uint64_t getNSSpeedsSize()
-  {
-    return t_sstorage.nsSpeeds.size();
-  }
-  static void submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now)
-  {
-    t_sstorage.nsSpeeds[server].submit(ca, usec, now);
-  }
-  static void clearNSSpeeds()
-  {
-    t_sstorage.nsSpeeds.clear();
-  }
-  static float getNSSpeed(const DNSName& server, const ComboAddress& ca)
-  {
-    return t_sstorage.nsSpeeds[server].d_collection[ca].peek();
-  }
+
+  static void pruneNSSpeeds(time_t limit);
+  static uint64_t getNSSpeedsSize();
+  static void submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now);
+  static void clearNSSpeeds();
+  static float getNSSpeed(const DNSName& server, const ComboAddress& ca);
+
   static EDNSStatus::EDNSMode getEDNSStatus(const ComboAddress& server)
   {
     const auto& it = t_sstorage.ednsstatus.find(server);