const time_t now = time(nullptr);
- for (const auto& e : g_stats.entries) {
- str << namespace_name << "." << hostname << "." << instance_name << "." << e.first << ' ';
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
- str << (*val)->load();
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
- str << (*adval)->load();
- }
- else if (const auto& dval = boost::get<double*>(&e.second)) {
- str << **dval;
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- str << (*func)(e.first);
+ {
+ auto entries = g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' ';
+ if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+ str << (*val)->load();
+ }
+ else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ str << (*adval)->load();
+ }
+ else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+ str << **dval;
+ }
+ else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+ str << (*func)(entry.d_name);
+ }
+ str << ' ' << now << "\r\n";
}
- str << ' ' << now << "\r\n";
}
auto states = g_dstates.getLocal();
boost::format fmt("%-35s\t%+11s");
g_outputBuffer.clear();
- auto entries = g_stats.entries;
+ auto entries = *g_stats.entries.read_lock();
sort(entries.begin(), entries.end(),
- [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
- return a.first < b.first;
- });
+ [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
+ return a.d_name < b.d_name;
+ });
boost::format flt(" %9.1f");
- for (const auto& e : entries) {
+ for (const auto& entry : entries) {
string second;
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
+ if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
second = std::to_string((*val)->load());
}
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
+ else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
second = (flt % (*adval)->load()).str();
}
- else if (const auto& dval = boost::get<double*>(&e.second)) {
+ else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
second = (flt % (**dval)).str();
}
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- second = std::to_string((*func)(e.first));
+ else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+ second = std::to_string((*func)(entry.d_name));
}
- if (leftcolumn.size() < g_stats.entries.size()/2) {
- leftcolumn.push_back((fmt % e.first % second).str());
+ if (leftcolumn.size() < entries.size() / 2) {
+ leftcolumn.push_back((fmt % entry.d_name % second).str());
}
else {
- rightcolumn.push_back((fmt % e.first % second).str());
+ rightcolumn.push_back((fmt % entry.d_name % second).str());
}
}
luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
setLuaNoSideEffect();
std::unordered_map<string, uint64_t> res;
- for (const auto& entry : g_stats.entries) {
- if (const auto& val = boost::get<pdns::stat_t*>(&entry.second))
- res[entry.first] = (*val)->load();
+ {
+ auto entries = g_stats.entries.read_lock();
+ res.reserve(entries->size());
+ for (const auto& entry : *entries) {
+ if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+ res[entry.d_name] = (*val)->load();
+ }
+ }
}
return res;
});
});
luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
- if (!checkConfigurationTime("declareMetric")) {
- return false;
- }
if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
g_outputBuffer = "Unable to declare metric '" + name + "': invalid name\n";
errlog("Unable to declare metric '%s': invalid name", name);
return false;
}
if (type == "counter") {
- auto itp = g_stats.customCounters.emplace(name, 0);
+ auto customCounters = g_stats.customCounters.write_lock();
+ auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()});
if (itp.second) {
- g_stats.entries.emplace_back(name, &g_stats.customCounters[name]);
+ g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customCounters)[name].d_value});
addMetricDefinition(name, "counter", description, customName ? *customName : "");
}
}
else if (type == "gauge") {
- auto itp = g_stats.customGauges.emplace(name, 0.);
+ auto customGauges = g_stats.customGauges.write_lock();
+ auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()});
if (itp.second) {
- g_stats.entries.emplace_back(name, &g_stats.customGauges[name]);
+ g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value});
addMetricDefinition(name, "gauge", description, customName ? *customName : "");
}
}
}
return true;
});
- luaCtx.writeFunction("incMetric", [](const std::string& name) {
- auto metric = g_stats.customCounters.find(name);
- if (metric != g_stats.customCounters.end()) {
- return ++(metric->second);
+ luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto metric = customCounters->find(name);
+ if (metric != customCounters->end()) {
+ if (step) {
+ metric->second.d_value += *step;
+ return metric->second.d_value.load();
+ }
+ return ++(metric->second.d_value);
}
g_outputBuffer = "incMetric no such metric '" + name + "'\n";
errlog("Unable to incMetric: no such name '%s'", name);
return (uint64_t)0;
});
luaCtx.writeFunction("decMetric", [](const std::string& name) {
- auto metric = g_stats.customCounters.find(name);
- if (metric != g_stats.customCounters.end()) {
- return --(metric->second);
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto metric = customCounters->find(name);
+ if (metric != customCounters->end()) {
+ return --(metric->second.d_value);
}
g_outputBuffer = "decMetric no such metric '" + name + "'\n";
errlog("Unable to decMetric: no such name '%s'", name);
return (uint64_t)0;
});
- luaCtx.writeFunction("setMetric", [](const std::string& name, const double& value) {
- auto metric = g_stats.customGauges.find(name);
- if (metric != g_stats.customGauges.end()) {
- metric->second = value;
- return value;
+ luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double {
+ {
+ auto customGauges = g_stats.customGauges.read_lock();
+ auto metric = customGauges->find(name);
+ if (metric != customGauges->end()) {
+ metric->second.d_value = value;
+ return value;
+ }
}
g_outputBuffer = "setMetric no such metric '" + name + "'\n";
errlog("Unable to setMetric: no such name '%s'", name);
return 0.;
});
luaCtx.writeFunction("getMetric", [](const std::string& name) {
- auto counter = g_stats.customCounters.find(name);
- if (counter != g_stats.customCounters.end()) {
- return (double)counter->second.load();
+ {
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto counter = customCounters->find(name);
+ if (counter != customCounters->end()) {
+ return (double)counter->second.d_value.load();
+ }
}
- else {
- auto gauge = g_stats.customGauges.find(name);
- if (gauge != g_stats.customGauges.end()) {
- return gauge->second.load();
+ {
+ auto customGauges = g_stats.customGauges.read_lock();
+ auto gauge = customGauges->find(name);
+ if (gauge != customGauges->end()) {
+ return gauge->second.d_value.load();
}
}
g_outputBuffer = "getMetric no such metric '" + name + "'\n";
std::ostringstream output;
static const std::set<std::string> metricBlacklist = { "special-memory-usage", "latency-count", "latency-sum" };
- for (const auto& e : g_stats.entries) {
- const auto& metricName = std::get<0>(e);
+ {
+ auto entries = g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ const auto& metricName = entry.d_name;
- if (metricBlacklist.count(metricName) != 0) {
- continue;
- }
+ if (metricBlacklist.count(metricName) != 0) {
+ continue;
+ }
- MetricDefinition metricDetails;
- if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
- vinfolog("Do not have metric details for %s", metricName);
- continue;
- }
+ MetricDefinition metricDetails;
+ if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+ vinfolog("Do not have metric details for %s", metricName);
+ continue;
+ }
- const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
- if (prometheusTypeName.empty()) {
- vinfolog("Unknown Prometheus type for %s", metricName);
- continue;
- }
+ const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+ if (prometheusTypeName.empty()) {
+ vinfolog("Unknown Prometheus type for %s", metricName);
+ continue;
+ }
- // Prometheus suggest using '_' instead of '-'
- std::string prometheusMetricName;
- if (metricDetails.customName.empty()) {
- prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
- }
- else {
- prometheusMetricName = metricDetails.customName;
- }
+ // Prometheus suggest using '_' instead of '-'
+ std::string prometheusMetricName;
+ if (metricDetails.customName.empty()) {
+ prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
+ }
+ else {
+ prometheusMetricName = metricDetails.customName;
+ }
- // for these we have the help and types encoded in the sources:
- output << "# HELP " << prometheusMetricName << " " << metricDetails.description << "\n";
- output << "# TYPE " << prometheusMetricName << " " << prometheusTypeName << "\n";
- output << prometheusMetricName << " ";
+ // for these we have the help and types encoded in the sources
+ // but we need to be careful about labels in custom metrics
+ std::string helpName = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
+ output << "# HELP " << helpName << " " << metricDetails.description << "\n";
+ output << "# TYPE " << helpName << " " << prometheusTypeName << "\n";
+ output << prometheusMetricName << " ";
- if (const auto& val = boost::get<pdns::stat_t*>(&std::get<1>(e))) {
- output << (*val)->load();
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&std::get<1>(e))) {
- output << (*adval)->load();
- }
- else if (const auto& dval = boost::get<double*>(&std::get<1>(e))) {
- output << **dval;
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e))) {
- output << (*func)(std::get<0>(e));
- }
+ if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+ output << (*val)->load();
+ }
+ else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ output << (*adval)->load();
+ }
+ else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+ output << **dval;
+ }
+ else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+ output << (*func)(entry.d_name);
+ }
- output << "\n";
+ output << "\n";
+ }
}
// Latency histogram buckets
static void addStatsToJSONObject(Json::object& obj)
{
- for (const auto& e : g_stats.entries) {
- if (e.first == "special-memory-usage") {
+ auto entries = g_stats.entries.read_lock();
+ for (const auto& entry : *entries) {
+ if (entry.d_name == "special-memory-usage") {
continue; // Too expensive for get-all
}
- if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
- obj.emplace(e.first, (double)(*val)->load());
- } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
- obj.emplace(e.first, (*adval)->load());
- } else if (const auto& dval = boost::get<double*>(&e.second)) {
- obj.emplace(e.first, (**dval));
- } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
- obj.emplace(e.first, (double)(*func)(e.first));
+ if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (double)(*val)->load());
+ } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (*adval)->load());
+ } else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (**dval));
+ } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+ obj.emplace(entry.d_name, (double)(*func)(entry.d_name));
}
}
}
resp.status = 200;
Json::array doc;
- for(const auto& item : g_stats.entries) {
- if (item.first == "special-memory-usage")
- continue; // Too expensive for get-all
+ {
+ auto entries = g_stats.entries.read_lock();
+ for (const auto& item : *entries) {
+ if (item.d_name == "special-memory-usage") {
+ continue; // Too expensive for get-all
+ }
- if (const auto& val = boost::get<pdns::stat_t*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (double)(*val)->load() }
- });
- }
- else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (*adval)->load() }
- });
- }
- else if (const auto& dval = boost::get<double*>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (**dval) }
- });
- }
- else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.second)) {
- doc.push_back(Json::object {
- { "type", "StatisticItem" },
- { "name", item.first },
- { "value", (double)(*func)(item.first) }
- });
+ if (const auto& val = boost::get<pdns::stat_t*>(&item.d_value)) {
+ doc.push_back(Json::object {
+ { "type", "StatisticItem" },
+ { "name", item.d_name },
+ { "value", (double)(*val)->load() }
+ });
+ }
+ else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.d_value)) {
+ doc.push_back(Json::object {
+ { "type", "StatisticItem" },
+ { "name", item.d_name },
+ { "value", (*adval)->load() }
+ });
+ }
+ else if (const auto& dval = boost::get<double*>(&item.d_value)) {
+ doc.push_back(Json::object {
+ { "type", "StatisticItem" },
+ { "name", item.d_name },
+ { "value", (**dval) }
+ });
+ }
+ else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.d_value)) {
+ doc.push_back(Json::object {
+ { "type", "StatisticItem" },
+ { "name", item.d_name },
+ { "value", (double)(*func)(item.d_name) }
+ });
+ }
}
}
Json my_json = doc;
double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
- typedef std::function<uint64_t(const std::string&)> statfunction_t;
- typedef boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t> entry_t;
+ using statfunction_t = std::function<uint64_t(const std::string&)>;
+ using entry_t = boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t>;
+ struct EntryPair
+ {
+ std::string d_name;
+ entry_t d_value;
+ };
- std::vector<std::pair<std::string, entry_t>> entries{
+ SharedLockGuarded<std::vector<EntryPair>> entries{std::vector<EntryPair>{
{"responses", &responses},
{"servfail-responses", &servfailResponses},
{"queries", &queries},
// Latency histogram
{"latency-sum", &latencySum},
{"latency-count", &latencyCount},
+ }};
+ struct MutableCounter
+ {
+ MutableCounter() = default;
+ MutableCounter(MutableCounter&& rhs): d_value(rhs.d_value.load())
+ {
+ }
+
+ mutable stat_t d_value{0};
+ };
+ struct MutableGauge
+ {
+ MutableGauge() = default;
+ MutableGauge(MutableGauge&& rhs): d_value(rhs.d_value.load())
+ {
+ }
+
+ mutable pdns::stat_t_trait<double> d_value{0};
};
- std::map<std::string, stat_t, std::less<>> customCounters;
- std::map<std::string, pdns::stat_t_trait<double>, std::less<>> customGauges;
+ SharedLockGuarded<std::map<std::string, MutableCounter, std::less<>>> customCounters;
+ SharedLockGuarded<std::map<std::string, MutableGauge, std::less<>>> customGauges;
};
extern struct DNSDistStats g_stats;
void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value) __attribute__ ((visibility ("default")));
double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) __attribute__ ((visibility ("default")));
void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen)
{
- auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customCounters.end()) {
- ++metric->second;
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
+ if (metric != customCounters->end()) {
+ ++metric->second.d_value;
+ }
+}
+
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value)
+{
+ auto customCounters = g_stats.customCounters.write_lock();
+ auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
+ if (metric != customCounters->end()) {
+ metric->second.d_value += value;
}
}
void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen)
{
- auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customCounters.end()) {
- --metric->second;
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
+ if (metric != customCounters->end()) {
+ --metric->second.d_value;
}
}
void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value)
{
- auto metric = g_stats.customGauges.find(std::string_view(metricName, metricNameLen));
- if (metric != g_stats.customGauges.end()) {
- metric->second = value;
+ auto customGauges = g_stats.customGauges.read_lock();
+ auto metric = customGauges->find(std::string_view(metricName, metricNameLen));
+ if (metric != customGauges->end()) {
+ metric->second.d_value = value;
}
}
{
auto name = std::string_view(metricName, metricNameLen);
if (isCounter) {
- auto counter = g_stats.customCounters.find(name);
- if (counter != g_stats.customCounters.end()) {
- return (double)counter->second.load();
+ auto customCounters = g_stats.customCounters.read_lock();
+ auto counter = customCounters->find(name);
+ if (counter != customCounters->end()) {
+ return (double)counter->second.d_value.load();
}
}
else {
- auto gauge = g_stats.customGauges.find(name);
- if (gauge != g_stats.customGauges.end()) {
- return gauge->second.load();
+ auto customGauges = g_stats.customGauges.read_lock();
+ auto gauge = customGauges->find(name);
+ if (gauge != customGauges->end()) {
+ return gauge->second.d_value.load();
}
}
return 0.;
Custom Metrics
=====================================
-You can define at configuration time your own metrics that can be updated using Lua.
+You can define at your own metrics that can be updated using Lua.
-The first step is to declare a new metric using :func:`declareMetric`.
+The first step is to declare a new metric using :func:`declareMetric`. In 1.8.0 the declaration had to be done at configuration time, but since 1.8.1 it can be done at any point.
Then you can update those at runtime using the following functions, depending on the metric type:
.. versionadded:: 1.8.0
+ .. versionchanged:: 1.8.1
+ This function can now be used at runtime, instead of only at configuration time.
+
Return true if declaration was successful
:param str name: The name of the metric, lowercase alphanumerical characters and dashes (-) only
:param str name: The description of the metric
:param str prometheusName: The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_``.
-.. function:: incMetric(name) -> int
+.. function:: incMetric(name [, step]) -> int
.. versionadded:: 1.8.0
- Increment counter by one, will issue an error if the metric is not declared or not a ``counter``
+ .. versionchanged:: 1.8.1
+ Optional ``step`` parameter added.
+
+ Increment counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
Return the new value
:param str name: The name of the metric
+ :param int step: By how much the counter should be incremented, default to 1.
.. function:: decMetric(name) -> int
Return the new value
:param str name: The name of the metric
+ :param double value: The new value
initialCounter = getMetric("my-custom-counter")
initialGauge = getMetric("my-custom-counter")
incMetric("my-custom-counter")
+ incMetric("my-custom-counter", 41)
setMetric("my-custom-gauge", initialGauge + 1.3)
- if getMetric("my-custom-counter") ~= (initialCounter + 1) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
+ if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
return DNSAction.Spoof, '1.2.3.5'
end
return DNSAction.Spoof, '4.3.2.1'
end
function declareNewMetric(dq)
- if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should fail") then
- return DNSAction.Spoof, '1.2.3.4'
+ if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
+ return DNSAction.None
end
- return DNSAction.None
+ return DNSAction.Spoof, '1.2.3.4'
end
declareMetric("my-custom-counter", "counter", "Number of tests run")