From: Remi Gacogne Date: Fri, 16 Jun 2023 11:39:16 +0000 (+0200) Subject: dnsdist: Implement a FFI method to declare a new custom metric X-Git-Tag: rec-5.0.0-alpha1~144^2~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=67cbba12a6e203d17f1286421d8acfc1b13559d1;p=thirdparty%2Fpdns.git dnsdist: Implement a FFI method to declare a new custom metric --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 0510d9a1af..aea5a1b66e 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -47,6 +47,7 @@ #ifdef LUAJIT_VERSION #include "dnsdist-lua-ffi.hh" #endif /* LUAJIT_VERSION */ +#include "dnsdist-metrics.hh" #include "dnsdist-nghttp2.hh" #include "dnsdist-proxy-protocol.hh" #include "dnsdist-rings.hh" @@ -2914,89 +2915,49 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) }); luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional customName) { - 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 customCounters = g_stats.customCounters.write_lock(); - auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()}); - if (itp.second) { - 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 customGauges = g_stats.customGauges.write_lock(); - auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()}); - if (itp.second) { - g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value}); - addMetricDefinition(name, "gauge", description, customName ? *customName : ""); - } - } - else { - g_outputBuffer = "declareMetric unknown type '" + type + "'\n"; - errlog("Unable to declareMetric '%s': no such type '%s'", name, type); + auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional(*customName) : std::nullopt); + if (result) { + g_outputBuffer += *result + "\n"; + errlog("%s", *result); return false; } return true; }); luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional 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); + auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1); + if (const auto* errorStr = std::get_if(&result)) { + g_outputBuffer = *errorStr + "'\n"; + errlog("%s", *errorStr); + return static_cast(0); } - g_outputBuffer = "incMetric no such metric '" + name + "'\n"; - errlog("Unable to incMetric: no such name '%s'", name); - return (uint64_t)0; + return std::get(result); }); - luaCtx.writeFunction("decMetric", [](const std::string& name) { - auto customCounters = g_stats.customCounters.read_lock(); - auto metric = customCounters->find(name); - if (metric != customCounters->end()) { - return --(metric->second.d_value); + luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional step) { + auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1); + if (const auto* errorStr = std::get_if(&result)) { + g_outputBuffer = *errorStr + "'\n"; + errlog("%s", *errorStr); + return static_cast(0); } - g_outputBuffer = "decMetric no such metric '" + name + "'\n"; - errlog("Unable to decMetric: no such name '%s'", name); - return (uint64_t)0; + return std::get(result); }); 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; - } + auto result = dnsdist::metrics::setCustomGauge(name, value); + if (const auto* errorStr = std::get_if(&result)) { + g_outputBuffer = *errorStr + "'\n"; + errlog("%s", *errorStr); + return 0.; } - g_outputBuffer = "setMetric no such metric '" + name + "'\n"; - errlog("Unable to setMetric: no such name '%s'", name); - return 0.; + return std::get(result); }); luaCtx.writeFunction("getMetric", [](const std::string& name) { - { - auto customCounters = g_stats.customCounters.read_lock(); - auto counter = customCounters->find(name); - if (counter != customCounters->end()) { - return (double)counter->second.d_value.load(); - } - } - { - auto customGauges = g_stats.customGauges.read_lock(); - auto gauge = customGauges->find(name); - if (gauge != customGauges->end()) { - return gauge->second.d_value.load(); - } + auto result = dnsdist::metrics::getCustomMetric(name); + if (const auto* errorStr = std::get_if(&result)) { + g_outputBuffer = *errorStr + "'\n"; + errlog("%s", *errorStr); + return 0.; } - g_outputBuffer = "getMetric no such metric '" + name + "'\n"; - errlog("Unable to getMetric: no such name '%s'", name); - return 0.; + return std::get(result); }); } diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index e9c7ca2086..aaac2e2066 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -175,6 +175,7 @@ dnsdist_SOURCES = \ dnsdist-lua-web.cc \ dnsdist-lua.cc dnsdist-lua.hh \ dnsdist-mac-address.cc dnsdist-mac-address.hh \ + dnsdist-metrics.cc dnsdist-metrics.hh \ dnsdist-nghttp2.cc dnsdist-nghttp2.hh \ dnsdist-prometheus.hh \ dnsdist-protobuf.cc dnsdist-protobuf.hh \ @@ -269,6 +270,7 @@ testrunner_SOURCES = \ dnsdist-lua-network.cc dnsdist-lua-network.hh \ dnsdist-lua-vars.cc \ dnsdist-mac-address.cc dnsdist-mac-address.hh \ + dnsdist-metrics.cc dnsdist-metrics.hh \ dnsdist-nghttp2.cc dnsdist-nghttp2.hh \ dnsdist-protocols.cc dnsdist-protocols.hh \ dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \ diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h b/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h index 5e393a7b94..e7cc8fe873 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h +++ b/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h @@ -228,6 +228,7 @@ uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspa size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default"))); void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default"))); +bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) __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"))); diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index f42a01b6e1..bf46aadece 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -25,6 +25,7 @@ #include "dnsdist-ecs.hh" #include "dnsdist-lua-ffi.hh" #include "dnsdist-mac-address.hh" +#include "dnsdist-metrics.hh" #include "dnsdist-lua-network.hh" #include "dnsdist-lua.hh" #include "dnsdist-ecs.hh" @@ -1601,60 +1602,57 @@ void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet) } } +bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) +{ + if (name == nullptr || nameLen == 0 || type == nullptr || description == nullptr) { + return false; + } + auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional(customName) : std::nullopt); + if (result) { + return false; + } + return true; +} + void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) { - auto customCounters = g_stats.customCounters.read_lock(); - auto metric = customCounters->find(std::string_view(metricName, metricNameLen)); - if (metric != customCounters->end()) { - ++metric->second.d_value; + auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U); + if (const auto* errorStr = std::get_if(&result)) { + return; } } 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; + auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value); + if (const auto* errorStr = std::get_if(&result)) { + return; } } void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) { - auto customCounters = g_stats.customCounters.read_lock(); - auto metric = customCounters->find(std::string_view(metricName, metricNameLen)); - if (metric != customCounters->end()) { - --metric->second.d_value; + auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U); + if (const auto* errorStr = std::get_if(&result)) { + return; } } void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double 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 result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value); + if (const auto* errorStr = std::get_if(&result)) { + return; } } double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) { - auto name = std::string_view(metricName, metricNameLen); - if (isCounter) { - 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 customGauges = g_stats.customGauges.read_lock(); - auto gauge = customGauges->find(name); - if (gauge != customGauges->end()) { - return gauge->second.d_value.load(); - } + auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen)); + if (const auto* errorStr = std::get_if(&result)) { + return 0.; } - return 0.; + return std::get(result); } const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg) diff --git a/pdns/dnsdistdist/dnsdist-metrics.cc b/pdns/dnsdistdist/dnsdist-metrics.cc new file mode 100644 index 0000000000..0eed93566a --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-metrics.cc @@ -0,0 +1,116 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include + +#include "dnsdist-metrics.hh" +#include "dnsdist.hh" +#include "dnsdist-web.hh" + +namespace dnsdist::metrics { + +std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName) +{ + if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) { + return std::string("Unable to declare metric '") + std::string(name) + std::string("': invalid name\n"); + } + + if (type == "counter") { + auto customCounters = g_stats.customCounters.write_lock(); + auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()}); + if (itp.second) { + 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 customGauges = g_stats.customGauges.write_lock(); + auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()}); + if (itp.second) { + g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value}); + addMetricDefinition(name, "gauge", description, customName ? *customName : ""); + } + } + else { + return std::string("Unable to declare metric: unknown type '") + type + "'"; + } + return std::nullopt; +} + +std::variant incrementCustomCounter(const std::string_view& name, 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); + } + return std::string("Unable to increment custom metric '") + std::string(name) + "': no such metric"; +} + +std::variant decrementCustomCounter(const std::string_view& name, 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); + } + return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such metric"; +} + +std::variant setCustomGauge(const std::string_view& name, const double value) +{ + auto customGauges = g_stats.customGauges.read_lock(); + auto metric = customGauges->find(name); + if (metric != customGauges->end()) { + metric->second.d_value = value; + return value; + } + + return std::string("Unable to set metric '") + std::string(name) + "': no such metric"; +} + +std::variant getCustomMetric(const std::string_view& name) +{ + { + auto customCounters = g_stats.customCounters.read_lock(); + auto counter = customCounters->find(name); + if (counter != customCounters->end()) { + return static_cast(counter->second.d_value.load()); + } + } + { + auto customGauges = g_stats.customGauges.read_lock(); + auto gauge = customGauges->find(name); + if (gauge != customGauges->end()) { + return gauge->second.d_value.load(); + } + } + return std::string("Unable to get metric '") + std::string(name) + "': no such metric"; +} +} diff --git a/pdns/dnsdistdist/dnsdist-metrics.hh b/pdns/dnsdistdist/dnsdist-metrics.hh new file mode 100644 index 0000000000..88a812ea8e --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-metrics.hh @@ -0,0 +1,39 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace dnsdist::metrics +{ + using Error = std::string; + + [[nodiscard]] std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName); + [[nodiscard]] std::variant incrementCustomCounter(const std::string_view& name, uint64_t step); + [[nodiscard]] std::variant decrementCustomCounter(const std::string_view& name, uint64_t step); + [[nodiscard]] std::variant setCustomGauge(const std::string_view& name, const double value); + [[nodiscard]] std::variant getCustomMetric(const std::string_view& name); +} diff --git a/pdns/dnsdistdist/docs/reference/custommetrics.rst b/pdns/dnsdistdist/docs/reference/custommetrics.rst index e0af5f51c2..0f656b7f9c 100644 --- a/pdns/dnsdistdist/docs/reference/custommetrics.rst +++ b/pdns/dnsdistdist/docs/reference/custommetrics.rst @@ -41,10 +41,14 @@ Then you can update those at runtime using the following functions, depending on .. versionadded:: 1.8.0 - Decrement 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. + + Decrement 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 decremented, default to 1. .. function:: getMetric(name) -> double diff --git a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc index e6f0e1e21c..8a5a06763b 100644 --- a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc @@ -29,6 +29,11 @@ #include "dnsparser.hh" #include "dnswriter.hh" +bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customPrometheusName) +{ + return true; +} + BOOST_AUTO_TEST_SUITE(test_dnsdist_lua_ffi) BOOST_AUTO_TEST_CASE(test_Query)