From: Otto Moerbeek Date: Mon, 28 Mar 2022 11:29:25 +0000 (+0200) Subject: Use boost::mult-index for nsspeed table and make it shared. X-Git-Tag: rec-4.7.0-beta1~17^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=03d26a97f5b933cf77e5ef00c43483a071a41c4c;p=thirdparty%2Fpdns.git Use boost::mult-index for nsspeed table and make it shared. --- diff --git a/pdns/rec_channel_rec.cc b/pdns/rec_channel_rec.cc index 9ea9589dbc..ea1d454990 100644 --- a/pdns/rec_channel_rec.cc +++ b/pdns/rec_channel_rec.cc @@ -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(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); diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index be4ded3286..06c184be1d 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -891,7 +891,7 @@ static void doStats(void) g_log << Logger::Notice << "stats: throttle map: " << broadcastAccFunction(pleaseGetThrottleSize) << ", ns speeds: " - << broadcastAccFunction(pleaseGetNsSpeedsSize) << ", failed ns: " + << SyncRes::getNSSpeedsSize() << ", failed ns: " << SyncRes::getFailedServersSize() << ", ednsmap: " << broadcastAccFunction(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); diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 3f80f1caea..cc8eb80fef 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -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(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(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& 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 d_collection; + const DNSName d_name; + struct timeval d_lastget; +}; + +class nsspeeds_t : + public multi_index_container, member>, + ordered_non_unique, member> + >> +{ +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(); + 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::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 s_nsSpeeds; + struct SavedParentEntry { SavedParentEntry(const DNSName& name, map>&& 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(); + 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 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 > 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 SyncRes::getAddrs(const DNSName &qname, unsigned int depth, is only one or none at all in the current set. */ map 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> SyncRes::shuffleInSpeedOrder(NsSet std::vector> 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 SyncRes::shuffleForwardSpeed(const vector 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: "<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); diff --git a/pdns/syncres.hh b/pdns/syncres.hh index c9e3abc0f1..e6e25ebd64 100644 --- a/pdns/syncres.hh +++ b/pdns/syncres.hh @@ -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 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::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& 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 collection_t; - collection_t d_collection; - struct timeval d_lastget{0, 0}; // stores time - }; - - typedef std::unordered_map 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; @@ -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);