size_t d_ringsCapacity{10000};
size_t d_ringsNumberOfShards{10};
size_t d_ringsNbLockTries{5};
+ size_t d_ringsSamplingRate{0};
uint32_t d_socketUDPSendBuffer{0};
uint32_t d_socketUDPRecvBuffer{0};
uint32_t d_hashPerturbation{0};
if (options.count("recordResponses") > 0) {
config.d_ringsRecordResponses = boost::get<bool>(options.at("recordResponses"));
}
+ if (options.count("samplingRate") > 0) {
+ config.d_ringsSamplingRate = boost::get<uint64_t>(options.at("samplingRate"));
+ }
});
}
catch (const std::exception& exp) {
#include "dnsdist-rings.hh"
-void Rings::init(size_t capacity, size_t numberOfShards, size_t nbLockRetries, bool recordQueries, bool recordResponses)
+void Rings::init(const RingsConfiguration& config)
{
if (d_initialized.exchange(true)) {
throw std::runtime_error("Rings::init() should only be called once");
}
- d_capacity = capacity;
- d_numberOfShards = numberOfShards;
- d_nbLockTries = nbLockRetries;
- d_recordQueries = recordQueries;
- d_recordResponses = recordResponses;
+ d_capacity = config.capacity;
+ d_numberOfShards = config.numberOfShards;
+ d_nbLockTries = config.nbLockTries;
+ d_samplingRate = config.samplingRate;
+ d_recordQueries = config.recordQueries;
+ d_recordResponses = config.recordResponses;
if (d_numberOfShards <= 1) {
d_nbLockTries = 0;
}
}
return hit;
}
+
+bool Rings::shouldSkipDueToSampling()
+{
+ if (d_samplingRate == 0) {
+ return false;
+ }
+ auto counter = d_samplingCounter++;
+ return (counter % d_samplingRate) == 0;
+}
std::unordered_map<int, vector<boost::variant<string, double>>> getTopBandwidth(unsigned int numentries);
size_t numDistinctRequestors();
+ struct RingsConfiguration
+ {
+ size_t capacity{0};
+ size_t numberOfShards{1};
+ size_t nbLockTries{5};
+ size_t samplingRate{0};
+ bool recordQueries{true};
+ bool recordResponses{true};
+ };
+
/* This function should only be called at configuration time before any query or response has been inserted */
- void init(size_t capacity, size_t numberOfShards, size_t nbLockRetries = 5, bool recordQueries = true, bool recordResponses = true);
+ void init(const RingsConfiguration& config);
size_t getNumberOfShards() const
{
void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
{
+ if (shouldSkipDueToSampling()) {
+ return;
+ }
auto ourName = DNSName(name);
#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
dnsdist::MacAddress macaddress;
void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
{
+ if (shouldSkipDueToSampling()) {
+ return;
+ }
auto ourName = DNSName(name);
for (size_t idx = 0; idx < d_nbLockTries; idx++) {
auto& shard = getOneShard();
return d_recordResponses;
}
+ size_t getSamplingRate() const
+ {
+ return d_samplingRate;
+ }
+
std::vector<std::unique_ptr<Shard>> d_shards;
pdns::stat_t d_blockingQueryInserts{0};
pdns::stat_t d_blockingResponseInserts{0};
return wasFull;
}
+ bool shouldSkipDueToSampling();
+
static constexpr bool s_keepLockingStats{false};
std::atomic<size_t> d_nbQueryEntries{0};
size_t d_capacity{10000};
size_t d_numberOfShards{10};
size_t d_nbLockTries{5};
+ size_t d_samplingRate{0};
+ std::atomic<size_t> d_samplingCounter{0};
bool d_recordQueries{true};
bool d_recordResponses{true};
};
lua-name: "setRingBuffersOptions"
internal-field-name: "d_ringsRecordResponses"
runtime-configurable: false
+ - name: "sampling_rate"
+ type: "u64"
+ default: 0
+ description: "Set a sampling rate ``S`` so that only 1 out of ``S`` queries and responses are inserted into the rings, to keep a longer history without consuming too much memory while also being able to process it quickly. Default is 0 which means there is no sampling and all entries are inserted."
+ lua-name: "setRingBuffersOptions"
+ internal-field-name: "d_ringsSamplingRate"
+ runtime-configurable: false
incoming_tls_certificate_key_pair:
description: "A pair of TLS certificate and key, with an optional associated password"
{
const auto& config = dnsdist::configuration::getImmutableConfiguration();
- g_rings.init(config.d_ringsCapacity, config.d_ringsNumberOfShards, config.d_ringsNbLockTries, config.d_ringsRecordQueries, config.d_ringsRecordResponses);
- }
+ Rings::RingsConfiguration ringsConfig {
+ .capacity = config.d_ringsCapacity,
+ .numberOfShards = config.d_ringsNumberOfShards,
+ .nbLockTries = config.d_ringsNbLockTries,
+ .samplingRate = config.d_ringsSamplingRate,
+ .recordQueries = config.d_ringsRecordQueries,
+ .recordResponses = config.d_ringsRecordResponses,
+ };
+ g_rings.init(ringsConfig);
+ }
for (const auto& frontend : dnsdist::getFrontends()) {
setUpLocalBind(*frontend, setupLogger);
.. versionadded:: 1.8.0
+ .. versionchanged:: 2.1.0
+ ``samplingRate`` option added.
+
Set the rings buffers configuration
:param table options: A table with key: value pairs with options.
Options:
* ``lockRetries``: int - Set the number of shards to attempt to lock without blocking before giving up and simply blocking while waiting for the next shard to be available. Default to 5 if there is more than one shard, 0 otherwise
+ * ``samplingRate``: int - Set a sampling rate ``S`` so that only 1 out of ``S`` queries and responses are inserted into the rings, to keep a longer history without consuming too much memory while also being able to process it quickly. Default is 0 which means there is no sampling and all entries are inserted
* ``recordQueries``: boolean - Whether to record queries in the ring buffers. Default is true. Note that :func:`grepq`, several top* commands (:func:`topClients`, :func:`topQueries`, ...) and the :doc:`Dynamic Blocks <../guides/dynblocks>` require this to be enabled.
* ``recordResponses``: boolean - Whether to record responses in the ring buffers. Default is true. Note that :func:`grepq`, several top* commands (:func:`topResponses`, :func:`topSlow`, ...) and the :doc:`Dynamic Blocks <../guides/dynblocks>` require this to be enabled.
gettime(&now);
g_rings.reset();
- g_rings.init(10000, 10);
+ Rings::RingsConfiguration config {
+ .capacity = 10000U,
+ .numberOfShards = 10U,
+ };
+ g_rings.init(config);
BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
TestFixture()
{
g_rings.reset();
- g_rings.init(10000, 10);
+ Rings::RingsConfiguration config {
+ .capacity = 10000U,
+ .numberOfShards = 10U,
+ };
+ g_rings.init(config);
}
~TestFixture()
{
/* 100k entries, one shard */
g_rings.reset();
- g_rings.init(1000000, 1);
+ Rings::RingsConfiguration config {
+ .capacity = 1000000U,
+ };
+ g_rings.init(config);
size_t numberOfSeconds = 10;
size_t blockDuration = 60;
g_rings.reset();
/* 10M entries, only one shard */
- g_rings.init(10000000, 1);
+ Rings::RingsConfiguration config {
+ .capacity = 10000000U,
+ };
+ g_rings.init(config);
{
DynBlockRulesGroup dbrg;
static void test_ring(size_t maxEntries, size_t numberOfShards, size_t nbLockTries)
{
Rings rings;
- rings.init(maxEntries, numberOfShards, nbLockTries);
+ Rings::RingsConfiguration config {
+ .capacity = maxEntries,
+ .numberOfShards = numberOfShards,
+ .nbLockTries = nbLockTries,
+ };
+ rings.init(config);
size_t entriesPerShard = maxEntries / numberOfShards;
BOOST_CHECK_EQUAL(rings.getNumberOfShards(), numberOfShards);
dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
Rings rings;
- rings.init(numberOfEntries, numberOfShards, lockAttempts, true);
+ Rings::RingsConfiguration config {
+ .capacity = numberOfEntries,
+ .numberOfShards = numberOfShards,
+ .nbLockTries = lockAttempts,
+ };
+ rings.init(config);
#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
Rings::Query query({requestor, qname, now, dh, size, qtype, protocol, dnsdist::MacAddress(), false});
#else